package com.hmy.finance.service.core.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.hmy.ccp.error.exception.HMYRuntimeException;
import com.hmy.finance.api.bo.core.request.*;
import com.hmy.finance.api.bo.core.request.enums.FeeTypeEnum;
import com.hmy.finance.api.bo.core.response.*;
import com.hmy.finance.common.constants.CommonConstants;
import com.hmy.finance.common.enums.BusinessBillTypeEnum;
import com.hmy.finance.common.enums.PaymentStatusEnum;
import com.hmy.finance.common.enums.ProfitLossStatusEnum;
import com.hmy.finance.common.properties.ReceivableInventoryOverdueRule;
import com.hmy.finance.common.utils.FinanceCalculateUtil;
import com.hmy.finance.dal.mapper.*;
import com.hmy.finance.dal.po.*;
import com.hmy.finance.dal.po.json.OverdueDetailJson;
import com.hmy.finance.dal.po.json.ProfitLossInfoJson;
import com.hmy.finance.dal.po.json.WaiverRuleJson;
import com.hmy.finance.dal.po.result.OverdueAmountResultPo;
import com.hmy.finance.job.core.DailyFeeBillGenerationJobHandler;
import com.hmy.finance.manager.crm.OpportunityFeignClientRpc;
import com.hmy.finance.service.core.*;
import com.hmy.finance.service.core.dto.*;
import com.hmy.finance.service.core.rdto.RefreshContractAccountOfAgeRdto;
import com.hmy.finance.service.receipt.CustomerBalanceAccountService;
import com.hmy.finance.service.receipt.CustomerContactorBalanceAccountService;
import com.hmy.finance.service.receipt.RechargeBalanceAmountService;
import com.hmy.finance.service.receipt.dto.contactorAccount.ContactorInitialAccountDto;
import com.hmy.finance.service.receipt.dto.recharge.RechargeContractFeeBillDto;
import com.hmy.finance.service.receipt.rdto.contactorAccount.ContactorInitialAccountRdto;
import com.hmy.finance.service.receipt.rdto.recharge.RechargeContractFeeBillRdto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * 财务核心数据服务
 *
 * @author songkf
 */
@Slf4j
@Service
@RefreshScope
public class FinancialCoreDataServiceImpl implements IFinancialCoreDataService {
    private final PlatformTransactionManager manager;
    private final DefaultIdentifierGenerator identifierGenerator;
    private final DeviceRentalRecordMapper deviceRentalRecordMapper;
    private final ProfitLossRecordMapper profitLossRecordMapper;
    private final DeviceProfitLossDetailMapper deviceProfitLossDetailMapper;
    /**
     * 设备损益明细记录服务
     */
    private final IDeviceProfitLossDetailService deviceProfitLossDetailService;
    /**
     * 设备日流水服务
     */
    private final IDeviceDailyBillService deviceDailyBillService;
    /**
     * 合同日流水服务
     */
    private final IContractDailyBillService contractDailyBillService;
    /**
     * 应收盘点服务
     */
    private final IReceivableInventoryService receivableInventoryService;
    /**
     * 设备租赁记录服务
     */
    private final IDeviceRentalRecordService deviceRentalRecordService;
    private final OpportunityFeignClientRpc opportunityFeignClientRpc;
    private final DailyFeeBillGenerationJobHandler dailyFeeBillGenerationJobHandler;
    private final ContractTerminationStopRecordMapper contractTerminationStopRecordMapper;
    private final RechargeTransactionBillMapper rechargeTransactionBillMapper;

    private final RechargeBalanceAmountService rechargeBalanceAmountService;
    private final CustomerBalanceAccountService customerBalanceAccountService;
    private final CustomerContactorBalanceAccountService contactorBalanceAccountService;
    @Value("${hmy.finance.validation.flag}")
    private Boolean validationFlag;
    @Value("${hmy.finance.cancel-supplement-flag}")
    private Boolean cancelSupplementFlag;
    //5位小数，四舍五入
    private static final MathContext FIVE_MATH_CONTEXT = new MathContext(5, RoundingMode.HALF_UP);
    //3位小数，四舍五入
    private static final MathContext THREE_MATH_CONTEXT = new MathContext(3, RoundingMode.HALF_UP);

    public FinancialCoreDataServiceImpl(PlatformTransactionManager manager, DefaultIdentifierGenerator identifierGenerator, DeviceRentalRecordMapper deviceRentalRecordMapper, ProfitLossRecordMapper profitLossRecordMapper, DeviceProfitLossDetailMapper deviceProfitLossDetailMapper, IDeviceProfitLossDetailService deviceProfitLossDetailService, IDeviceDailyBillService deviceDailyBillService, IContractDailyBillService contractDailyBillService, IReceivableInventoryService receivableInventoryService, IDeviceRentalRecordService deviceRentalRecordService, OpportunityFeignClientRpc opportunityFeignClientRpc, DailyFeeBillGenerationJobHandler dailyFeeBillGenerationJobHandler, ContractTerminationStopRecordMapper contractTerminationStopRecordMapper, RechargeTransactionBillMapper rechargeTransactionBillMapper, RechargeBalanceAmountService rechargeBalanceAmountService, CustomerBalanceAccountService customerBalanceAccountService, CustomerContactorBalanceAccountService contactorBalanceAccountService) {
        this.manager = manager;
        this.identifierGenerator = identifierGenerator;
        this.deviceRentalRecordMapper = deviceRentalRecordMapper;
        this.profitLossRecordMapper = profitLossRecordMapper;
        this.deviceProfitLossDetailMapper = deviceProfitLossDetailMapper;
        this.deviceProfitLossDetailService = deviceProfitLossDetailService;
        this.deviceDailyBillService = deviceDailyBillService;
        this.contractDailyBillService = contractDailyBillService;
        this.receivableInventoryService = receivableInventoryService;
        this.deviceRentalRecordService = deviceRentalRecordService;
        this.opportunityFeignClientRpc = opportunityFeignClientRpc;
        this.dailyFeeBillGenerationJobHandler = dailyFeeBillGenerationJobHandler;
        this.contractTerminationStopRecordMapper = contractTerminationStopRecordMapper;
        this.rechargeTransactionBillMapper = rechargeTransactionBillMapper;
        this.rechargeBalanceAmountService = rechargeBalanceAmountService;
        this.customerBalanceAccountService = customerBalanceAccountService;
        this.contactorBalanceAccountService = contactorBalanceAccountService;
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public StartRentalResponse startRental(StartRentalRequest request) {
        StartRentalDto dto = StartRentalDto.buildStartRentalDto(request);
//        dto.setOpportunityFeignInfoResponse(opportunityFeignClientRpc.opportunityInfo(request.getOrderInfo().getOpportunityId()));
        // 1.1. 组装“设备租赁记录”数据集合
        dto.assembleRentalRecord(identifierGenerator);
        // 1.2 判断dto中的设备是否存在在租但退租时间为空的
        if (this.validationFlag && deviceRentalRecordMapper.selectCount(dto.getCheckRentalStatusWrapper()) > 0) {
            //抛出异常，提示“起租设备中存在未退租设备,请勿重复出租”
            throw new HMYRuntimeException(506001001);
        }
        // 1.2. 批量插入“设备租赁记录”
        // 1.3. 如果不成功，返回 Boolean.False
        Assert.isTrue(deviceRentalRecordMapper.insertBatch(dto.getDeviceRentalRecordPoList()) > 0, "506000101");
        // 1.4. 获取进场运费数据，组装成“损益业务记录”
        // 1.5. 获取进场运费减免数据，组装“损益业务记录”集合
        dto.assembleShippingFeeProfitLossRecord(identifierGenerator);
        // 1.6. 获取进场设备改装费数据，组装成“损益业务记录”
        // 1.7. 获取进场设备改装费减免数据，组装成“损益业务记录”集合
        dto.assembleDeviceModificationFeeProfitLossRecord(identifierGenerator);
        // 1.8. 批量插入“损益业务记录"
        // 1.9. 如果不成功，返回 Boolean.False，并回滚
        if (!dto.getProfitLossRecordPoList().isEmpty()) {
            Assert.isTrue(profitLossRecordMapper.insertBatch(dto.getProfitLossRecordPoList()) > 0, "506000102");
        }
        // 1.10. 批量插入“设备损益明细记录"
        // 1.11. 如果不成功，返回 Boolean.False，并回滚
        if (!dto.getDeviceProfitLossDetailPoList().isEmpty()) {
            Assert.isTrue(deviceProfitLossDetailMapper.insertBatch(dto.getDeviceProfitLossDetailPoList()) > 0, "506000103");
        }
        //Initial Customer & Contactor Balance Account
       /* CustomerInitialAccountDto customerInitialAccountDto = new CustomerInitialAccountDto();
        customerInitialAccountDto.setCustomerId(dto.getCustomerInfo().getCustomerId());
        customerInitialAccountDto.setCustomerName(dto.getCustomerInfo().getCustomerName());
        customerInitialAccountDto.setAmount(BigDecimal.ZERO);
        CustomerInitialAccountRdto customerInitialAccountRdto = customerBalanceAccountService.initialCustomerBalanceAccount(customerInitialAccountDto);
        if (!customerInitialAccountRdto.isSuccess()) {
            throw new HMYRuntimeException(506400001);
        }*/
        ContactorInitialAccountDto contactorInitialAccountDto = new ContactorInitialAccountDto();
        contactorInitialAccountDto.setCustomerId(dto.getCustomerInfo().getCustomerId());
        contactorInitialAccountDto.setCustomerName(dto.getCustomerInfo().getCustomerName());
        contactorInitialAccountDto.setContactorId(dto.getCustomerInfo().getSignatoryId());
        contactorInitialAccountDto.setContactorName(dto.getCustomerInfo().getSignatoryName());
        contactorInitialAccountDto.setAmount(BigDecimal.ZERO);
        ContactorInitialAccountRdto contactorInitialAccountRdto = contactorBalanceAccountService.initialCustomerContactorBalanceAccount(contactorInitialAccountDto);
        if (!contactorInitialAccountRdto.isSuccess()) {
            throw new HMYRuntimeException(506500001);
        }
        // 1.11. 批量插入“设备费用流水”
        // 1.12. 处理设备费用流水，如果起租时间早于当前日期，则把当前合同的起租时间到昨天的费用流水跑出来
        if (dto.getEntryFormInfo().getRentalStartTime().toLocalDate().isBefore(LocalDate.now()) && !cancelSupplementFlag) {
            log.debug("进场时间早于当前时间,需要补生成日账单.补生成日期区间:{}-{}", dto.getEntryFormInfo().getRentalStartTime().toLocalDate(), LocalDate.now().minusDays(1));
            //在事务提交后执行
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                @Override
                public void afterCommit() {

                    dailyFeeBillGenerationJobHandler.generateDailyInvoiceBill(dto.getEntryFormInfo().getRentalStartTime().toLocalDate(), LocalDate.now().minusDays(1), dto.getContractInfo().getContractId());
                }
            });
        }
        // 1.12. 如果成功，返回 Boolean.Ture
        return dto.getResponse();
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public TerminationRentalResponse terminationRental(TerminationRentalRequest request) {
        TerminationRentalDto dto = TerminationRentalDto.buildTerminationRentalDto(request);
        //2.1. 根据合同 ID 以及设备 ID 集合查询出已退场“设备租赁记录”集合
        //判断退租设备是否全部在租
        if (this.validationFlag && this.deviceRentalRecordMapper.selectCount(dto.getCheckRentalStatusWrapper()) > 0) {
            //抛出异常，提示“存在未起租或已退租的设备，请核查数据”
            throw new HMYRuntimeException(506001002);
        }
        //查询dto中的contractId 的任意一条设备租赁记录，获取其中的运费减免条件，set到dto中的exitShippingFeeWaiverList
        dto.setExitShippingFeeWaiverList(this.deviceRentalRecordMapper.selectList(Wrappers.lambdaQuery(DeviceRentalRecordPo.class).eq(DeviceRentalRecordPo::getContractId, request.getContractId())));
        dto.setRentingDeviceList(this.deviceRentalRecordMapper.selectList(dto.getRentingDeviceListQueryWrapper()));

        //2.2. 批量更新“设备租赁记录”，设置退租时间
//        dto.batchSetRentingDevicesTerminationTime(identifierGenerator);
        dto.getRentingDeviceList().forEach(deviceRentalRecordPo -> {
            dto.setCustomerId(deviceRentalRecordPo.getCustomerId());
            //设置退租时间
            deviceRentalRecordPo.setRentalEndTime(dto.getTerminationTime());
            deviceRentalRecordPo.setExitFormId(dto.getExitFormId());
            deviceRentalRecordPo.setExitShippingInfo(dto.acceptShippingInfo());
            //设置设备费用结算周期结束时间
            deviceRentalRecordPo.setExpenseCycleEndTime(dto.getTerminationTime().toLocalDate().isBefore(LocalDate.now()) ? LocalDateTime.now() : dto.getTerminationTime());
            dto.setTotalRentalMonthlyPrice(dto.getTotalRentalMonthlyPrice().add(deviceRentalRecordPo.getMonthlyRentalPrice()));


            //记录进场单ID
            dto.getEntryFormIdList().add(deviceRentalRecordPo.getEntryFormId());

           /*
           与熊伟和产品杜伟刚沟通后，暂时不做对应处理。后续作为优化需求采用更优方案上线
           //判断设备租赁记录是否存在未处理完成的报停天数，如果有则生成对应的损益费用
            if (deviceRentalRecordPo.getTotalRemainingPauseDays() != null && deviceRentalRecordPo.getTotalRemainingPauseDays() > 0) {
                BigDecimal pauseWaiverFee = getExitPauseWaiverAmount(deviceRentalRecordPo);
                ProfitLossRecordPo profitLossRecordPo = new ProfitLossRecordPo();
                profitLossRecordPo.setContractId(contractId);
                profitLossRecordPo.setCustomerId(deviceRentalRecordPo.getCustomerId());
                profitLossRecordPo.setBusinessBillId(exitFormId);
                profitLossRecordPo.setBusinessBillType(BusinessBillTypeEnum.EXIT);
                profitLossRecordPo.setFeeType(FeeTypeEnum.EXIT_PAUSE_FEE_WAIVER);
                profitLossRecordPo.setTotalFeeAmount(pauseWaiverFee);
                profitLossRecordPo.setFeeIncurredTime(LocalDateTime.now());
                profitLossRecordPo.setStatus(ProfitLossStatusEnum.WAIT_EFFECTIVE);
                profitLossRecordPo.setId(identifierGenerator.nextId(profitLossRecordPo));
                profitLossRecordPoList.add(profitLossRecordPo);
                //设备损益明细记录
                DeviceProfitLossDetailPo deviceProfitLossDetailPo = assembleDeviceProfitLossRecord(identifierGenerator, profitLossRecordPo.getId(), FeeTypeEnum.EXIT_PAUSE_FEE_WAIVER, deviceRentalRecordPo, pauseWaiverFee);
                deviceProfitLossDetailPoList.add(deviceProfitLossDetailPo);
                //剩余未生效报停天数清零
                deviceRentalRecordPo.setTotalRemainingPauseDays(0d);
            }*/
        });
        /*
         * 退场成功后，需要判断退场时间在今天之前，如果在今天之前，需要将设备的日费用流水置为失效状态（fee_status设置为false）
         * 并且记录在退场时间（包含）-今天（不包含）之间的费用流水中的损益信息，将损益信息的状态更改为待生效（WAIT_EFFECTIVE）
         */
        if (dto.getTerminationTime().toLocalDate().isBefore(LocalDate.now())) {
            dto.setPendingDeviceDailyBillList(this.deviceDailyBillService.queryDeviceDailyBillListByRentalRecordIdAndDateArea(dto.getRentingDeviceList().stream().map(DeviceRentalRecordPo::getId).collect(Collectors.toList()), dto.getTerminationTime().toLocalDate(), LocalDate.now()));
            dto.setPendingContractDailyBillList(this.contractDailyBillService.queryContractDailyBillListByContractIdAndDateArea(dto.getContractId(), dto.getTerminationTime().toLocalDate(), LocalDate.now()));
            //清除已产生的设备日费用流水
            Boolean updateContractFeeStatusResult = contractDailyBillService.clearContractDailyBill(dto.getContractId(), dto.getTerminationTime().toLocalDate());

            Boolean updateFeeStatusResult = deviceDailyBillService.clearDeviceDailyBill(dto.getRentingDeviceList(), dto.getTerminationTime().toLocalDate());
//            if (!updateFeeStatusResult) {
//                log.error("清除已产生的设备日费用流水失败，合同ID：{}", request.getContractId());
//                throw new HMYRuntimeException(506000106);
//            }
            //将费用流水中的损益信息的状态更改为待生效
            dto.getPendingDeviceDailyBillList().forEach(deviceDailyBillPo -> {
                deviceDailyBillPo.getProfitLossInfo().forEach(profitLossInfo -> {
                    dto.getPendingRecoveryDeviceProfitLossDetailIdList().add(profitLossInfo.getDetailId());
                });
            });
            //将合同费用流水中的损益信息的状态更改为待生效状态
            dto.getPendingContractDailyBillList().forEach(contractDailyBillPo -> {
                contractDailyBillPo.getProfitLossInfo().forEach(profitLossInfo -> {
                    //当损益信息的detailId为空时，才是合同维度的损益，才需要处理
                    if (profitLossInfo.getDetailId() == null) {
                        dto.getPendingRecoveryProfitLossRecordIdList().add(profitLossInfo.getRecordId());
                    }
                });
            });
            if (!dto.getPendingRecoveryDeviceProfitLossDetailIdList().isEmpty()) {
                DeviceProfitLossDetailPo deviceProfitLossDetailPo = new DeviceProfitLossDetailPo();
                deviceProfitLossDetailPo.setStatus(ProfitLossStatusEnum.WAIT_EFFECTIVE);
                deviceProfitLossDetailMapper.update(deviceProfitLossDetailPo, Wrappers.lambdaQuery(DeviceProfitLossDetailPo.class).in(DeviceProfitLossDetailPo::getId, dto.getPendingRecoveryDeviceProfitLossDetailIdList()));
            }
            if (!dto.getPendingRecoveryProfitLossRecordIdList().isEmpty()) {
                ProfitLossRecordPo profitLossRecordPo = new ProfitLossRecordPo();
                profitLossRecordPo.setStatus(ProfitLossStatusEnum.WAIT_EFFECTIVE);
                profitLossRecordMapper.update(profitLossRecordPo, Wrappers.lambdaQuery(ProfitLossRecordPo.class).in(ProfitLossRecordPo::getId, dto.getPendingRecoveryProfitLossRecordIdList()));
            }
            //需要将计租天数当天到退场时间当天之间的天数扣减掉
            dto.getRentingDeviceList().forEach(deviceRentalRecordPo -> {
                AtomicReference<Double> chargeDays = new AtomicReference<>(0d);
                AtomicReference<BigDecimal> subAmount = new AtomicReference<>(BigDecimal.ZERO);
                AtomicReference<BigDecimal> profitAmount = new AtomicReference<>(BigDecimal.ZERO);
                dto.getPendingDeviceDailyBillList().forEach(dailyBill -> {
                    if (dailyBill.getDeviceId().equals(deviceRentalRecordPo.getDeviceId())) {
                        chargeDays.updateAndGet(v -> v + dailyBill.getChargeDays());
                        subAmount.updateAndGet(v -> v.add(dailyBill.getFee()));
                        profitAmount.updateAndGet(v -> v.add(dailyBill.getTotalProfitLossAmount()));
                        //如果当日包含入场半天费用减免需要剔除损益，计算到租金中
                        dailyBill.getProfitLossInfo().forEach(profitLossInfo -> {
                            if (profitLossInfo.getType().equals(FeeTypeEnum.INITIAL_DAY_FEE_WAIVER.getValue())) {
                                chargeDays.updateAndGet(v -> v + (-0.5));
                                subAmount.updateAndGet(v -> v.subtract(profitLossInfo.getFee()));
                                profitAmount.updateAndGet(v -> v.add(profitLossInfo.getFee()));
                            }
                            if (profitLossInfo.getType().equals(FeeTypeEnum.INITIAL_DAY_FEE.getValue())) {
                                subAmount.updateAndGet(v -> v.add(profitLossInfo.getFee()));
                                profitAmount.updateAndGet(v -> v.subtract(profitLossInfo.getFee()));
                            }
                        });
                    }
                });
                deviceRentalRecordPo.setTotalRentalDays(deviceRentalRecordPo.getTotalRentalDays() - chargeDays.get());

                BigDecimal subtract = deviceRentalRecordPo.getTotalRentalAmount().subtract(subAmount.get());
                deviceRentalRecordPo.setTotalProfitLossAmount(deviceRentalRecordPo.getTotalProfitLossAmount().subtract(profitAmount.get()));
                deviceRentalRecordPo.setTotalRentalAmount(subtract.compareTo(BigDecimal.ZERO) > 0 ? subtract : BigDecimal.ZERO);
                if (deviceRentalRecordPo.getTotalRentalAmount().compareTo(deviceRentalRecordPo.getMonthlyRentalPrice()) < 0) {
                    deviceRentalRecordPo.setUseMonthlyRentalPrice(false);
                }
            });
            //产生联系人账户余额回充业务单据
            RechargeTransactionBillPo rechargeTransactionBillPo = dto.assembleRechargeTransactionBill();

            int insert = rechargeTransactionBillMapper.insert(rechargeTransactionBillPo);
            log.debug("产生联系人账户余额回充业务单据结果：{}", insert);
            if (insert > 0) {
                TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                    @Override
                    public void afterCommit() {
                        RechargeContractFeeBillRdto rechargeContractFeeBillRdto = rechargeBalanceAmountService.rechargeContractFeeBill(RechargeContractFeeBillDto.getInstance(rechargeTransactionBillPo.getContractId()));
                        log.debug("Contactor Account Balance Recharge Handle result：{}", rechargeContractFeeBillRdto);
                    }
                });
            }
        }
        //2.3. 获取退场运费数据，组装成“损益业务记录”
        dto.assembleShippingFeeProfitLossRecord(identifierGenerator);
        //2.4. 插入“损益业务记录”
        //2.5. 如果不成功，返回 Boolean.False，并回滚
        if (!dto.getProfitLossRecordPoList().isEmpty()) {
            Assert.isTrue(profitLossRecordMapper.insertBatch(dto.getProfitLossRecordPoList()) > 0, "506000104");
        }
        //2.6. 批量插入“设备损益明细记录"
        //2.7. 如果不成功，返回 Boolean.False，并回滚
        if (!dto.getDeviceProfitLossDetailPoList().isEmpty()) {
            Assert.isTrue(deviceProfitLossDetailMapper.insertBatch(dto.getDeviceProfitLossDetailPoList()) > 0, "506000105");
        }
        //2.8. 查询"设备租赁记录"中，属于本次退场的进场单ID，并且对应设备已全部退场的"设备租赁记录"集合
        dto.setAllExitedEntryFormIdList(this.deviceRentalRecordMapper.selectAllExitedEntryFormIdList(dto.getEntryFormIdList()));
        //2.9. 根据已退场“设备租赁记录”集合中的进场单ID，查询出待生效的业务类型为进场运费减免 /设备改装费减免的“损益业务记录”集合
        dto.setPendingProfitLossRecordList(this.profitLossRecordMapper.selectList(dto.getPendingProfitLossRecordQueryWrapper()));
        dto.setPendingDeviceProfitLossDetailList(this.deviceProfitLossDetailMapper.selectList(dto.getPendingDeviceProfitLossDetailQueryWrapper()));
        //2.10. 批量更新已全部退场的进场单下待生效的业务类型为进场运费减免 /设备改装费减免“损益业务记录”为已失效
        //2.11. 如果不成功，返回 Boolean.False，并回滚
        if (!dto.getPendingProfitLossRecordList().isEmpty()) {
            Assert.isTrue(profitLossRecordMapper.batchInvalidProfitLossById(dto.getPendingProfitLossRecordList().stream().map(ProfitLossRecordPo::getId).collect(Collectors.toList())) > 0, "506000106");
        }
        if (!dto.getPendingDeviceProfitLossDetailList().isEmpty()) {
            DeviceProfitLossDetailPo deviceProfitLossDetailPo = new DeviceProfitLossDetailPo();
            deviceProfitLossDetailPo.setStatus(ProfitLossStatusEnum.ALREADY_INVALID);
            Assert.isTrue(deviceProfitLossDetailMapper.update(deviceProfitLossDetailPo, Wrappers.lambdaQuery(DeviceProfitLossDetailPo.class).in(DeviceProfitLossDetailPo::getId, dto.getPendingDeviceProfitLossDetailList().stream().map(DeviceProfitLossDetailPo::getId).collect(Collectors.toList()))) > 0, "506000106");
        }
        //2.12. 更新“设备租赁记录”中，属于本次退场的进场单ID，并且对应设备已全部退场的“设备租赁记录”集合的状态为已退场
        Assert.isTrue(deviceRentalRecordMapper.batchSetRentingDevicesExited(dto.getRentingDeviceList()) > 0, "506000106");
        if (dto.getTerminationTime().toLocalDate().isBefore(LocalDate.now()) && !cancelSupplementFlag) {
            //在事务提交后执行
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                @Override
                public void afterCommit() {
                    dailyFeeBillGenerationJobHandler.generateDailyInvoiceBill(dto.getTerminationTime().toLocalDate(), LocalDate.now(), dto.getContractId());
                }
            });
        }
        //2.13. 如果成功，返回 Boolean.Ture
        return dto.getResponse();
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public BaseGenerateProfitLossResponse rentalAdjustmentProfitLoss(ReceivableAdjustGenerateRequest request) {
        ReceivableAdjustGenerateDto dto = ReceivableAdjustGenerateDto.buildReceivableAdjustGenerateDto(request);
        //3.1. 组装“损益业务记录”，将状态设置为待生效
        dto.assembleProfitLossRecord(identifierGenerator);
        //查询合同下的所有设备租赁记录
        dto.setDeviceRentalRecordPoList(this.deviceRentalRecordMapper.selectList(dto.getDeviceRentalRecordPoListQueryWrapper()));
        //3.2. 插入“损益业务记录”
        //3.3. 如果不成功，返回 Boolean.False，并回滚
        Assert.isTrue(profitLossRecordMapper.insertBatch(dto.getProfitLossRecordPoList()) > 0, "506000107");
        if (!dto.getDeviceRentalRecordPoList().isEmpty()) {
            //3.4. 批量更新“设备租赁记录”中，费用周期的结束时间
            Assert.isTrue(deviceRentalRecordMapper.batchUpdateExpenseCycleEndTime(dto.getDeviceRentalRecordPoList()) > 0, "506000108");
        }
        //3.4. 如果成功，返回 Boolean.Ture
        return BaseGenerateProfitLossResponse.success();
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public BaseGenerateProfitLossResponse claimGenerateProfitLoss(ClaimGenerateRequest request) {
        ClaimGenerateDto dto = ClaimGenerateDto.buildClaimGenerateDto(request);
        //判断索赔的设备是否是已租设备
        if (this.deviceRentalRecordMapper.selectCount(dto.getCheckRentalStatusWrapper()) <= 0) {
            throw new HMYRuntimeException(506001003);
        }
        //3.1. 组装“损益业务记录”，将状态设置为待生效
        dto.setDeviceRentalRecordPo(this.deviceRentalRecordMapper.selectOne(dto.buildDeviceRentalRecordPoLambdaQueryWrapper()));
        dto.assembleProfitLossRecord(identifierGenerator);
        //3.2. 插入“损益业务记录”
        //3.3. 如果不成功，返回 Boolean.False，并回滚
        Assert.isTrue(profitLossRecordMapper.insertBatch(dto.getProfitLossRecordPoList()) > 0, "506000108");
        //3.4. 批量插入“设备损益明细记录"
        //3.5. 如果不成功，返回 Boolean.False，并回滚
        Assert.isTrue(deviceProfitLossDetailMapper.insertBatch(dto.getDeviceProfitLossDetailPoList()) > 0, "506000109");
        if (dto.getNeedUpdateDeviceRentalRecord()) {
            //3.6. 更新“设备租赁记录”中，计费周期的结束时间
            Assert.isTrue(deviceRentalRecordMapper.updateById(dto.getDeviceRentalRecordPo()) > 0, "506000109");
        }
        //3.7. 如果成功，返回 Boolean.Ture
        return BaseGenerateProfitLossResponse.success();
    }

    /**
     * Pause the generation of profit and loss, and perform various checks and operations.
     *
     * @param request the request object for pausing profit and loss generation
     * @return the response object indicating the success of pausing profit and loss generation
     */
    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public BaseGenerateProfitLossResponse pauseGenerateProfitLoss(PauseGenerateRequest request) {
        PauseGenerateDto dto = PauseGenerateDto.buildPauseGenerateDto(request);
        //判断报停的设备是否是已租设备
        if (this.deviceRentalRecordMapper.selectCount(dto.getCheckRentalStatusWrapper()) <= 0) {
            throw new HMYRuntimeException(506001004);
        }
        //3.1. 查询设备租赁记录集合
        dto.setDeviceRentalRecordPoList(this.deviceRentalRecordMapper.selectList(dto.buildDeviceRentalRecordPoLambdaQueryWrapper()));
        //3.2. 组装“损益业务记录”，将状态设置为待生效
        dto.assembleProfitLossRecord(identifierGenerator, deviceProfitLossDetailMapper);
        //3.2. 插入“损益业务记录”
        //3.3. 如果不成功，返回 Boolean.False，并回滚
        Assert.isTrue(profitLossRecordMapper.insertBatch(dto.getProfitLossRecordPoList()) > 0, "506000110");
        //3.4. 批量插入“设备损益明细记录"
        //3.5. 如果不成功，返回 Boolean.False，并回滚
        Assert.isTrue(deviceProfitLossDetailMapper.insertBatch(dto.getDeviceProfitLossDetailPoList()) > 0, "506000111");
        //3.4. 如果成功，返回 Boolean.Ture
        return BaseGenerateProfitLossResponse.success();
    }

    @Override
    public QueryDeviceRentalRecordRemainingPauseDayResponse queryDeviceRentalRecordRemainingPauseDay(QueryDeviceRentalRecordRemainingPauseDayRequest request) {
        QueryDeviceRentalRecordRemainingPauseDayDto dto = QueryDeviceRentalRecordRemainingPauseDayDto.buildQueryDeviceRentalRecordRemainingPauseDayDto(request);
        //4.1. 查询设备租赁记录
        dto.setDeviceRentalRecord(this.deviceRentalRecordMapper.selectOne(dto.buildDeviceRentalRecordPoLambdaQueryWrapper()));
        //4.2. 如果设备租赁记录不存在，返回 null
        Assert.notNull(dto.getDeviceRentalRecord(), "506000112");
        //4.3. 查询未生效的设备损益记录
        dto.setDeviceProfitLossDetailPoList(deviceProfitLossDetailMapper.selectList(dto.buildDeviceProfitLossDetailPoLambdaQueryWrapper()));
        //4.2. 返回结果
        return dto.getResponse();
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public ContractTerminationStopFlowAndOffsetResponse contractTerminationStopFlowAndOffset(ContractTerminationStopFlowAndOffsetRequest request) {
        ContractTerminationStopFlowAndOffsetDto dto = ContractTerminationStopFlowAndOffsetDto.buildFromRequest(request);
        //1.1 查询合同下的所有在租设备集合
        dto.setDeviceRentalRecordPoList(this.deviceRentalRecordMapper.selectList(dto.getDeviceRentalRecordPoListQueryWrapper()));
        //1.2 如果合同下没有在租设备，返回失败
        if (dto.getDeviceRentalRecordPoList().isEmpty()) {
            return ContractTerminationStopFlowAndOffsetResponse.fail();
        }
        //1.3 构建合同退场停止流水和冲抵记录
        dto.buildContractTerminationStopFlowAndOffsetRecord(identifierGenerator);
        //1.4 插入合同退场停止流水和冲抵记录
        Assert.isTrue(contractTerminationStopRecordMapper.insert(dto.getContractTerminationStopRecordPo()) > 0, "506000113");
        //1.5 更新设备租赁记录的状态为停止流水以及停止冲抵
        dto.assembleDeviceRentalRecordUpdateInfo();
        Assert.isTrue(deviceRentalRecordMapper.batchUpdateDeviceRentalRecordStop(dto.getDeviceRentalRecordPoList()) > 0, "506000114");
        return ContractTerminationStopFlowAndOffsetResponse.success();
    }

    @Override
    public ContractCancelStopFlowAndOffsetResponse contractCancelStopFlowAndOffset(ContractCancelStopFlowAndOffsetRequest request) {
        return null;
    }

    @Override
    public SupplementaryPauseAmountCalculateResponse calculateSupplementaryPauseDeviceFee(SupplementaryPauseAmountCalculateRequest request) {
        //1. 构建Dto
        SupplementaryPauseAmountCalculateDto dto = SupplementaryPauseAmountCalculateDto.buildFromRequest(request);
        //2. 查询租赁记录
        dto.setDeviceRentalRecordPoList(this.deviceRentalRecordMapper.selectList(dto.buildDeviceRentalRecordPoLambdaQueryWrapper()));
        //2.1 如果租赁记录不存在，抛出异常
//        Assert.notEmpty(dto.getDeviceRentalRecordPoList(), "506000115");
        //3. 计算各个设备的报停费用
        dto.calculateSupplementaryPauseTotalAmount();
        //4. 返回结果
        return dto.getResponse();
    }

    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public SupplementaryPauseGenerateResponse generateSupplementaryPause(SupplementaryPauseGenerateRequest request) {
        //1. 构建Dto
        SupplementaryPauseGenerateDto dto = SupplementaryPauseGenerateDto.buildFromRequest(request);
        //2. 查询租赁记录
        dto.setDeviceRentalRecordPoList(this.deviceRentalRecordMapper.selectList(dto.buildDeviceRentalRecordPoLambdaQueryWrapper()));
        //2.1 如果租赁记录不存在，抛出异常
        Assert.notEmpty(dto.getDeviceRentalRecordPoList(), "506000118");
        //3. 组装损益业务记录并组装设备损益明细记录
        dto.assembleProfitLossRecord(identifierGenerator);
        //4. 批量插入损益业务记录
        Assert.isTrue(profitLossRecordMapper.insert(dto.getProfitLossRecordPo()) > 0, "506000118");
        Assert.isTrue(deviceProfitLossDetailMapper.insertBatch(dto.getDeviceProfitLossRecordPoList()) > 0, "506000118");
        //5.更新设备租赁记录的费用结算周期结束时间
        if (!dto.getDeviceRentalRecordPoList().isEmpty()) {
            Assert.isTrue(deviceRentalRecordMapper.batchUpdateExpenseCycleEndTime(dto.getDeviceRentalRecordPoList()) > 0, "506000118");
        }
        return dto.getResponse();
    }

    @Override
    public RefreshContractAccountOfAgeRdto refreshContractAccountOfAge(RefreshContractAccountOfAgeDto refreshDto) {
        if (Objects.isNull(refreshDto.getContractIdList()) || refreshDto.getContractIdList().isEmpty()) {
            // if contractIdList is empty, throw exception.
            throw new HMYRuntimeException(506001005);
        }
        RefreshContractAccountOfAgeRdto refreshRdto = new RefreshContractAccountOfAgeRdto();
        refreshRdto.setContractRefreshResultList(new ArrayList<>(refreshDto.getContractIdList().size()));
        refreshRdto.setAllSuccess(Boolean.TRUE);
        //loop refreshDto#contractIdList,refresh account age of contract
        refreshDto.getContractIdList().forEach(contractId -> {
            Boolean contractRefreshResult = refreshAccountOfAge(contractId);
            refreshRdto.setAllSuccess(contractRefreshResult && refreshRdto.getAllSuccess());
            RefreshContractAccountOfAgeRdto.ContractRefreshResult contractRefreshResultDto = new RefreshContractAccountOfAgeRdto.ContractRefreshResult();
            contractRefreshResultDto.setContractId(contractId);
            contractRefreshResultDto.setSuccess(contractRefreshResult);
            refreshRdto.getContractRefreshResultList().add(contractRefreshResultDto);
        });
        return refreshRdto;
    }

    @Transactional(rollbackFor = RuntimeException.class)
    public Boolean refreshAccountOfAge(Long contractId) {
        //step 1. select device rental record list by contract id
        List<DeviceRentalRecordPo> deviceRentalRecordPoList = deviceRentalRecordMapper.selectList(Wrappers.lambdaQuery(DeviceRentalRecordPo.class).eq(DeviceRentalRecordPo::getContractId, contractId));
        if (CollectionUtils.isEmpty(deviceRentalRecordPoList)) {
            // if deviceRentalRecordPoList is empty, return false.
            return Boolean.FALSE;
        }
        // Step 2. loop device rental record list ,generate device rental record overdue info map
        List<DeviceDailyBillPo> allDeviceDailyBillPoList = new ArrayList<>();
        deviceRentalRecordPoList.forEach(rentalRecordPo -> {
            Map<String, DailyInvoiceGenerationJobDto.PeriodInterval> accountPeriodIntervalMap = new HashMap<>();
            generateDeviceOverdueInfo(rentalRecordPo.getOrderExtensionInfo().getSettlementMethod(), rentalRecordPo.getOrderExtensionInfo().getAccountPeriod(), rentalRecordPo, accountPeriodIntervalMap, rentalRecordPo.getOrderExtensionInfo().getAccountPeriodDays());
            log.debug("Generate device overdue info success, device rental record id:{},account Period Interval Map:{}", rentalRecordPo.getId(), accountPeriodIntervalMap);
            //Step 2.1. select device rental record daily bill list by device rental record id
            List<DeviceDailyBillPo> deviceDailyBillPoList = deviceDailyBillService.queryDeviceDailyBillListByRentalRecordIdAndDateArea(Collections.singletonList(rentalRecordPo.getId()), null, null);
            if (CollectionUtils.isEmpty(deviceDailyBillPoList)) {
                // if deviceDailyBillPoList is empty, return false.
                return;
            }
            //Step 2.2. Calculate the overdue amount of the device rental record with the accountPeriodIntervalMap
            List<OverdueDetailJson> overdueDetailJsons = calculateDeviceOverdueAmount(rentalRecordPo.getId(), accountPeriodIntervalMap, LocalDate.now());
            rentalRecordPo.setOverdueInfoList(overdueDetailJsons);
            allDeviceDailyBillPoList.addAll(deviceDailyBillPoList);
        });
        //Step 3.Select Contract Receivable Inventory, and calculate contract overdue info

        Boolean refreshResult = receivableInventoryService.refreshReceivableInventory(contractId, allDeviceDailyBillPoList, deviceRentalRecordPoList);
        if (!refreshResult) {
            return Boolean.FALSE;
        }
        //Step 4. Batch update device rental record info
        return deviceRentalRecordMapper.updateDeviceRentalRecordFeeAmount(deviceRentalRecordPoList) > 0;
    }

    private List<OverdueDetailJson> calculateDeviceOverdueAmount(Long deviceRentalRecordId, Map<String, DailyInvoiceGenerationJobDto.PeriodInterval> accountPeriodIntervalMap, LocalDate handleDate) {
        List<OverdueDetailJson> overdueDetailList = new ArrayList<>();
        accountPeriodIntervalMap.forEach((key, value) -> {
            //逾期账期
            OverdueDetailJson overdueDetailDto = new OverdueDetailJson();
            overdueDetailDto.setOverdueDaysDesc(key);
            if (value != null) {
                if (value.getOverdueRule() != null) {
                    overdueDetailDto.setOverdueDaysStart(value.getOverdueRule().getStartDays());
                    overdueDetailDto.setOverdueDaysEnd(value.getOverdueRule().getEndDays());
                    overdueDetailDto.setOverdueStartDate(value.getStart());
                    overdueDetailDto.setOverdueEndDate(value.getEnd());
                    overdueDetailDto.setOverdueDaysKey(value.getOverdueRule().getRuleKey());
                }
                //根据value中的start和end 查询当前设备ID所有费用信息（月租价格、日租价格、总损益）
                OverdueAmountResultPo overdueAmount = this.deviceRentalRecordMapper.selectDeviceOverdueAmount(deviceRentalRecordId, value.getStart() != null ? value.getStart().atStartOfDay() : null, value.getEnd() != null ? value.getEnd().atTime(LocalTime.MAX) : null);

                //判断设备租赁记录是否已使用月租单价
                overdueDetailDto.setOverdueAmount(overdueAmount.getRentalAmount());
                //判断dto的handleDate是否处于value的start与end之间
                if ((value.getStart() != null && (handleDate.isAfter(value.getStart())) && handleDate.isBefore(value.getEnd()))) {
                    //如果是，则将overdueDetailDto的overdueAmount累加上今日的费用
                    overdueDetailDto.setOverdueAmount(overdueDetailDto.getOverdueAmount());
                }
            } else {
                overdueDetailDto.setOverdueAmount(BigDecimal.ZERO);
            }
            overdueDetailList.add(overdueDetailDto);
        });
        return overdueDetailList;
    }


    //    @Transactional(rollbackFor = Exception.class)
    public void handleGenerateDailyBill(DailyInvoiceGenerationJobDto dto) {
        log.debug("[处理时间:{}]-开始处理生成每日费用流水，合同信息：{}", DateUtil.now(), dto.getContractExtensionInfoJson());
        TransactionDefinition definition = new DefaultTransactionDefinition();


        //删除操作，开启事务
        TransactionStatus deleteStatus = manager.getTransaction(definition);
        try {
            //处理生成每日费用流水
            //处理生成设备每日费用流水
            handleGenerateDeviceDailyInvoice(dto);
            //处理生成合同每日费用流水
            handleGenerateContractDailyInvoice(dto);
            //处理应收盘点
            handleContractReceivableInventory(dto);
            log.debug("[处理时间:{}]-完成处理生成每日费用流水，合同信息：{}", DateUtil.now(), dto.getContractExtensionInfoJson());
            //todo 数据库操作
            manager.commit(deleteStatus);
        } catch (Exception e) {
            manager.rollback(deleteStatus);
            throw e;
        }

    }

    /**
     * 处理应收盘点
     *
     * @param dto 每日流水处理任务数据传输对象
     * @author songkf
     */
    public void handleContractReceivableInventory(DailyInvoiceGenerationJobDto dto) {
        log.debug("[处理时间:{}]-[合同编号:{}]-开始处理应收盘点", DateUtil.now(), dto.getContractExtensionInfoJson().getContractNo());
        //根据合同ID，查询应收盘点
        receivableInventoryService.handleReceivableInventory(dto);
        log.debug("[处理时间:{}]-[合同编号:{}]-完成处理应收盘点", DateUtil.now(), dto.getContractExtensionInfoJson().getContractNo());
    }

    /**
     * 处理生成合同每日费用流水
     *
     * @param dto 每日流水处理任务数据传输对象
     * @author songkf
     */
    public void handleGenerateContractDailyInvoice(DailyInvoiceGenerationJobDto dto) {
        log.debug("[处理时间:{}]-[合同编号:{}]-开始处理生成合同每日费用流水", DateUtil.now(), dto.getContractExtensionInfoJson().getContractNo());
        ContractDailyBillPo contractDailyBill = new ContractDailyBillPo();
        ContractDailyBillPo contractDailyBillPo = contractDailyBillService.queryContractDailyBillByContractIdWithDate(dto.getContractExtensionInfoJson().getContractId(), dto.getHandleDate());
        if (contractDailyBillPo != null) {
            log.debug("[处理时间:{}]-[合同编号:{}]-合同已生成过{}的日费用流水", DateUtil.now(), dto.getContractExtensionInfoJson().getContractNo(), dto.getHandleDate());
            dto.setContractDailyBillPo(contractDailyBillPo);
            return;
        }
        List<DeviceDailyBillPo> todayDeviceDailyBill = deviceDailyBillService.queryDeviceDailyBillListByContractIdAndDateArea(dto.getContractExtensionInfoJson().getContractId(), dto.getHandleDate(), dto.getHandleDate());
        todayDeviceDailyBill.addAll(dto.getDeviceDailyBillList());
        contractDailyBill.setProfitLossInfo(new ArrayList<>());
        contractDailyBill.setContractId(dto.getContractExtensionInfoJson().getContractId());
        contractDailyBill.setCustomerId(dto.getCustomerExtensionInfoJson().getCustomerId());
        contractDailyBill.setDailyFee(dto.getDeviceDailyBillList().stream().map(DeviceDailyBillPo::getDailyFee).reduce(BigDecimal.ZERO, BigDecimal::add));
        contractDailyBill.setMonthlyFee(dto.getDeviceDailyBillList().stream().map(DeviceDailyBillPo::getMonthlyFee).reduce(BigDecimal.ZERO, BigDecimal::add));
        contractDailyBill.setFee(todayDeviceDailyBill.stream().map(DeviceDailyBillPo::getFee).reduce(BigDecimal.ZERO, BigDecimal::add));
        //遍历dto.getDeviceDailyBillList()，取出每日费用流水中的损益明细，根据类型相同合并到contractDailyBill中
        contractDailyBill.setProfitLossInfo(new ArrayList<>());
        todayDeviceDailyBill.forEach(deviceDailyBill -> deviceDailyBill.getProfitLossInfo().forEach(profitLossInfoJson -> {
            //根据类型查找contractDailyBill中是否存在相同类型的损益明细
            ProfitLossInfoJson profitLossInfo = contractDailyBill.getProfitLossInfo().stream().filter(profitLossInfoJson1 -> profitLossInfoJson1 != null && profitLossInfoJson1.getType().equals(profitLossInfoJson.getType()) && profitLossInfoJson1.getBusinessBillType().equals(profitLossInfoJson.getBusinessBillType())).findFirst().orElse(null);
            if (profitLossInfo == null) {
                //不存在相同类型的损益明细，直接添加
                contractDailyBill.getProfitLossInfo().add(profitLossInfoJson);
            } else {
                //存在相同类型的损益明细，累加金额
                profitLossInfo.setFee(profitLossInfo.getFee().add(profitLossInfoJson.getFee()));
            }
        }));
        final BigDecimal[] receivableAdjustTotalAmount = {BigDecimal.ZERO};
        //查询应收调整的损益业务记录
        List<ProfitLossRecordPo> receivableAdjustProfitLossRecordList = profitLossRecordMapper.selectList(dto.getReceivableAdjustProfitLossRecordQueryWrapper());
        //遍历receivableAdjustProfitLossRecordList，将应收调整的损益业务记录添加到contractDailyBill中
        receivableAdjustProfitLossRecordList.forEach(profitLossRecordPo -> {
            //根据类型查找contractDailyBill中是否存在相同类型的损益明细
            ProfitLossInfoJson profitLossInfo = contractDailyBill.getProfitLossInfo().stream().filter(profitLossInfoJson -> profitLossInfoJson != null && profitLossInfoJson.getType().equals(profitLossRecordPo.getFeeType().getValue()) && profitLossInfoJson.getBusinessBillType().equals(profitLossRecordPo.getBusinessBillType().getValue())).findFirst().orElse(null);
            if (profitLossInfo == null) {
                //不存在相同类型的损益明细，直接添加
                contractDailyBill.getProfitLossInfo().add(ProfitLossInfoJson.getInstance(profitLossRecordPo.getBusinessBillType().getValue(), profitLossRecordPo.getFeeType().getValue(), profitLossRecordPo.getFeeType().getDesc(), profitLossRecordPo.getTotalFeeAmount()));
            } else {
                //存在相同类型的损益明细，累加金额 todo 这边根据实际运行情况验证
                profitLossInfo.setFee(profitLossInfo.getFee().add(profitLossRecordPo.getTotalFeeAmount()));
            }
            profitLossRecordPo.setStatus(ProfitLossStatusEnum.ALREADY_EFFECTIVE);
        });
        List<ProfitLossRecordPo> receivableAdjustProfitLossRecordListAll = profitLossRecordMapper.selectList(Wrappers.<ProfitLossRecordPo>lambdaQuery().eq(ProfitLossRecordPo::getContractId, dto.getContractExtensionInfoJson().getContractId()).eq(ProfitLossRecordPo::getBusinessBillType, BusinessBillTypeEnum.RECEIVABLE_ADJUSTMENT));
        //遍历所有的应收调整，将应收调整计算到总数中
        receivableAdjustProfitLossRecordListAll.forEach(profitLossRecordPo -> {
            if (WAIVER_FEE_TYPE_LIST.contains(profitLossRecordPo.getFeeType())) {
                receivableAdjustTotalAmount[0] = receivableAdjustTotalAmount[0].subtract(profitLossRecordPo.getTotalFeeAmount());
            } else {
                receivableAdjustTotalAmount[0] = receivableAdjustTotalAmount[0].add(profitLossRecordPo.getTotalFeeAmount());
            }
        });
        contractDailyBill.setTotalProfitLossAmount(contractDailyBill.getProfitLossInfo().stream().map(item -> {
            if (WAIVER_FEE_TYPE_LIST.contains(FeeTypeEnum.getFeeTypeEnumByValue(item.getType()))) {
                return item.getFee().negate();
            }
            return item.getFee();
        }).reduce(BigDecimal.ZERO, BigDecimal::add));

        //查询出该合同下所有的设备租赁记录集合
        List<DeviceRentalRecordPo> deviceRentalRecordList = deviceRentalRecordMapper.selectList(Wrappers.<DeviceRentalRecordPo>lambdaQuery().eq(DeviceRentalRecordPo::getContractId, dto.getContractExtensionInfoJson().getContractId()));
        //设置未对账金额，累加每日费用流水中的未对账金额
        contractDailyBill.setTotalUnreconciledAmount(deviceRentalRecordList.stream().map(DeviceRentalRecordPo::getTotalUnreconciledAmount).reduce(BigDecimal.ZERO, BigDecimal::add));
        contractDailyBill.setTotalUnreconciledAmount(contractDailyBill.getTotalUnreconciledAmount().add(receivableAdjustTotalAmount[0]));
        //查询ProfitLossRecordPo中已对账的应收调整业务单据类型的损益业务记录集合
        List<ProfitLossRecordPo> profitLossRecordPoList = profitLossRecordMapper.selectList(Wrappers.<ProfitLossRecordPo>lambdaQuery().eq(ProfitLossRecordPo::getContractId, dto.getContractExtensionInfoJson().getContractId()).eq(ProfitLossRecordPo::getBusinessBillType, BusinessBillTypeEnum.RECEIVABLE_ADJUSTMENT).eq(ProfitLossRecordPo::getStatus, ProfitLossStatusEnum.RECONCILED.getValue()));
        //设置总账单的已对账总金额，累加每日费用流水中的已对账金额
        contractDailyBill.setHistoryReconciledAmount(deviceRentalRecordList.stream().map(DeviceRentalRecordPo::getHistoryReconciledAmount).reduce(BigDecimal.ZERO, BigDecimal::add));
        //设置已对账金额，累加每日费用流水中的已对账金额
        contractDailyBill.setTotalReconciledAmount(deviceRentalRecordList.stream().map(DeviceRentalRecordPo::getTotalReconciledAmount).reduce(BigDecimal.ZERO, BigDecimal::add));
        //contractDailyBill的historyReconciledAmount、totalReconciledAmount累加profitLossRecordPoList的totalFeeAmount
        contractDailyBill.setHistoryReconciledAmount(contractDailyBill.getHistoryReconciledAmount().add(profitLossRecordPoList.stream().map(ProfitLossRecordPo::getTotalFeeAmount).reduce(BigDecimal.ZERO, BigDecimal::add)));
        contractDailyBill.setTotalReconciledAmount(contractDailyBill.getTotalReconciledAmount().add(profitLossRecordPoList.stream().map(ProfitLossRecordPo::getTotalFeeAmount).reduce(BigDecimal.ZERO, BigDecimal::add)));
        //设置总营收，累加每日费用流水中的总营收
        contractDailyBill.setTotalRevenueAmount(deviceRentalRecordList.stream().map(DeviceRentalRecordPo::getTotalRevenueAmount).reduce(BigDecimal.ZERO, BigDecimal::add));
        contractDailyBill.setTotalRevenueAmount(contractDailyBill.getTotalRevenueAmount().add(receivableAdjustTotalAmount[0]));
        //设置已回款金额，默认为0
        contractDailyBill.setTotalPaymentAmount(todayDeviceDailyBill.stream().map(DeviceDailyBillPo::getPaidAmount).reduce(BigDecimal.ZERO, BigDecimal::add));
        //设置回款状态，默认未回款
        PaymentStatusEnum paidStatus = contractDailyBill.getTotalPaymentAmount().compareTo(BigDecimal.ZERO) > 0 ? (contractDailyBill.getTotalPaymentAmount().compareTo(contractDailyBill.getFee()) < 0 ? PaymentStatusEnum.PARTIAL_PAYMENT : PaymentStatusEnum.PAID) : PaymentStatusEnum.UNPAID;
        contractDailyBill.setPaymentStatus(paidStatus);
        //设置费用发生时间
        contractDailyBill.setFeeIncurredTime(dto.getHandleDate().atStartOfDay());
        boolean inserted = contractDailyBillService.save(contractDailyBill);
        log.debug("[处理时间:{}]-[合同编号:{}]-插入合同每日费用流水，{}", DateUtil.now(), dto.getContractExtensionInfoJson().getContractNo(), inserted ? "成功" : "失败");
        dto.setContractDailyBillPo(contractDailyBill);
        log.debug("[处理时间:{}]-[合同编号:{}]-完成处理生成合同每日费用流水", DateUtil.now(), dto.getContractExtensionInfoJson().getContractNo());
        if (receivableAdjustProfitLossRecordList.isEmpty()) {
            return;
        }
        //更新应收调整的使用状态
        ProfitLossRecordPo updatePo = new ProfitLossRecordPo();
        updatePo.setStatus(ProfitLossStatusEnum.ALREADY_EFFECTIVE);
        profitLossRecordMapper.update(updatePo, Wrappers.<ProfitLossRecordPo>lambdaUpdate().in(ProfitLossRecordPo::getId, receivableAdjustProfitLossRecordList.stream().map(ProfitLossRecordPo::getId).collect(Collectors.toList())));
    }

    /**
     * 处理生成设备每日费用流水
     *
     * @param dto 每日流水处理任务数据传输对象
     * @author songkf
     */
    public void handleGenerateDeviceDailyInvoice(DailyInvoiceGenerationJobDto dto) {
        log.debug("[处理时间:{}]-[合同编号:{}]-开始处理生成设备每日费用流水,处理的数据日期是:{}", DateUtil.now(), dto.getContractExtensionInfoJson().getContractNo(), dto.getHandleDate());
        //根据合同ID，查询所有在租设备及今日退租的设备租赁记录
        dto.setDeviceRentalRecordList(this.deviceRentalRecordService.queryRentingDeviceRentalRecordByContractId(dto.getContractExtensionInfoJson().getContractId(), dto.getHandleDate()));
        log.debug("[处理时间:{}]-[合同编号:{}]-查询所有在租设备及今日退租的设备租赁记录，共{}条", DateUtil.now(), dto.getContractExtensionInfoJson().getContractNo(), dto.getDeviceRentalRecordList().size());
        //遍历设备租赁记录，生成每日费用流水
        dto.setDeviceDailyBillList(this.generateDeviceDailyInvoiceBill(dto));
        //批量插入设备每日费用流水
        if (!dto.getDeviceDailyBillList().isEmpty()) {
            int saved = this.deviceDailyBillService.saveBatchDeviceDailyBill(dto.getDeviceDailyBillList());
            log.debug("[处理时间:{}]-[合同编号:{}]-批量插入设备每日费用流水，共{}条", DateUtil.now(), dto.getContractExtensionInfoJson().getContractNo(), saved);
            int updated = this.deviceRentalRecordService.updateDeviceRentalRecordFeeAmount(dto.getDeviceRentalRecordList());
            log.debug("[处理时间:{}]-[合同编号:{}]-批量更新设备租赁记录的费用信息，共{}条", DateUtil.now(), dto.getContractExtensionInfoJson().getContractNo(), updated);
        }
        log.debug("[处理时间:{}]-[合同编号:{}]-完成处理生成设备每日费用流水", DateUtil.now(), dto.getContractExtensionInfoJson().getContractNo());
    }


    //================== 以下是一些辅助方法 ==================

    /**
     * 10点
     */
    private final static LocalTime TEN_AM = LocalTime.of(10, 0);
    /**
     * 15:30
     */
    private final static LocalTime THREE_THIRTY_PM = LocalTime.of(15, 30);
    //减免费用类型
    private final static List<FeeTypeEnum> WAIVER_FEE_TYPE_LIST = Arrays.asList(FeeTypeEnum.INITIAL_DAY_FEE_WAIVER, FeeTypeEnum.SUPPLEMENTARY_PAUSE_FEE, FeeTypeEnum.EXIT_PAUSE_FEE_WAIVER, FeeTypeEnum.RENT_FEE_WAIVER, FeeTypeEnum.PAUSE_FEE_WAIVER, FeeTypeEnum.SHIPPING_FEE_WAIVER, FeeTypeEnum.MODIFICATION_FEE_WAIVER, FeeTypeEnum.CLAIMS_FEE_WAIVER);

    /**
     * Generate the device daily invoice bill based on the provided DailyInvoiceGenerationJobDto.
     *
     * @param dto the DailyInvoiceGenerationJobDto containing device rental record list and handle date
     * @return the list of DeviceDailyBillPo generated for each device rental record
     */
    private List<DeviceDailyBillPo> generateDeviceDailyInvoiceBill(DailyInvoiceGenerationJobDto dto) {

        List<DeviceDailyBillPo> list = new ArrayList<>();

        List<DeviceRentalRecordPo> deviceRentalRecordList = dto.getDeviceRentalRecordList();
        for (int i = 0; i < deviceRentalRecordList.size(); i++) {
            DeviceRentalRecordPo rentalRecordPo = deviceRentalRecordList.get(i);
            DeviceDailyBillPo deviceDailyBill = getDeviceDailyBillPo(dto, rentalRecordPo, deviceRentalRecordList, i);
            if (deviceDailyBill == null) {
                continue;
            }
            list.add(deviceDailyBill);
        }
        dto.setDeviceRentalRecordList(deviceRentalRecordList);
        return list;
    }

    private DeviceDailyBillPo getDeviceDailyBillPo(DailyInvoiceGenerationJobDto dto, DeviceRentalRecordPo rentalRecordPo, List<DeviceRentalRecordPo> deviceRentalRecordList, int i) {
        dto.setOrderExtensionInfoJson(rentalRecordPo.getOrderExtensionInfo());
        //判断是否已经生成过每日费用流水
        if (deviceDailyBillService.queryDeviceDailyBillByDeviceRentalRecordIdWithDate(rentalRecordPo.getId(), dto.getHandleDate()) != null) {
            log.debug("[处理时间:{}]-[设备编号:{}]-设备已生成过{}的日费用流水", DateUtil.now(), rentalRecordPo.getDeviceNo(), dto.getHandleDate());
            return null;
        }
        //生成设备每日费用流水
        log.debug("[处理时间:{}]-[设备编号:{}]-生成设备每日费用流水,开始", DateUtil.now(), rentalRecordPo.getDeviceNo());
        //设备租赁单价，默认使用日租单价计算，当发生日转月时，使用月租单价计算
        BigDecimal rentalPrice = rentalRecordPo.getDailyRentalPrice();
        double rate = 1;
        Boolean isPauseDay = Boolean.FALSE;
        //======== 计算已租天数 - 开始 ==========
        Boolean isTermination = Boolean.FALSE;
        //判断是否退租
        if (rentalRecordPo.getRentalEndTime() != null && dto.getHandleDate().equals(rentalRecordPo.getRentalEndTime().toLocalDate())) {
            isTermination = Boolean.TRUE;
            //已退租
            double terminationDay = getTerminationDay(rentalRecordPo.getRentalEndTime());
            log.debug("[处理时间:{}]-[设备编号:{}]-设备已退租,退租时间:{},退租当天计租天数:{}", DateUtil.now(), rentalRecordPo.getDeviceNo(), rentalRecordPo.getRentalEndTime(), terminationDay);

            //======== 计算租金比例（退场10:00之前不算租金，10:00-15:30算0.5天租金，15:30之后算1天） - 开始 ==========
            rate = terminationDay;
            rentalRecordPo.setTotalRentalDays(rentalRecordPo.getTotalRentalDays() + terminationDay);
            rentalRecordPo.setTotalLeaseDays(rentalRecordPo.getTotalLeaseDays() + 1);
            //======== 计算租金比例（退场10:00之前不算租金，10:00-15:30算0.5天租金，15:30之后算1天） - 结束 ==========
            //如果设备总租赁天数小于1天，则默认为1天
            if (rentalRecordPo.getTotalRentalDays() < 1) {
                rate = 1 - rentalRecordPo.getTotalRentalDays();
                rentalRecordPo.setTotalRentalDays(1d);
                rentalRecordPo.setTotalLeaseDays(1d);
            }
        } else {
            //======== 计算租金比例（进场10:00之前1天，10:00-15:30算0.5天租金，15:30之后不算租金） - 开始 ==========
            if (dto.getHandleDate().equals(rentalRecordPo.getRentalStartTime().toLocalDate())) {
                rate = getTerminationDayStart(rentalRecordPo.getRentalStartTime());
                rentalRecordPo.setTotalRentalDays(rentalRecordPo.getTotalRentalDays() + rate);
                rentalRecordPo.setTotalLeaseDays(rentalRecordPo.getTotalLeaseDays() + 1);
            } else if (rentalRecordPo.getRentalEndTime() == null || rentalRecordPo.getRentalEndTime().toLocalDate().isAfter(dto.getHandleDate())) {
                //未退租
                log.debug("[处理时间:{}]-[设备编号:{}]-设备未退租", dto.getHandleDate(), rentalRecordPo.getDeviceNo());
                //设置总租赁天数,未退场的设备租赁记录，总租赁天数加1
                rentalRecordPo.setTotalRentalDays(rentalRecordPo.getTotalRentalDays() + 1);
                rentalRecordPo.setTotalLeaseDays(rentalRecordPo.getTotalLeaseDays() + 1);
                //======== 计算租金比例（进场10:00之前1天，10:00-15:30算0.5天租金，15:30之后不算租金） - 结束 ==========
            } else {
                //如果已退租，且未产生退租当日的费用流水，则需要将退租当日的费用计算到今天
                //查询退租当天的流水
                DeviceDailyBillPo deviceDailyBillPo = deviceDailyBillService.queryDeviceDailyBillByDeviceRentalRecordIdWithDate(rentalRecordPo.getId(), rentalRecordPo.getRentalEndTime().toLocalDate());
                if (Objects.isNull(deviceDailyBillPo)) {
                    isTermination = Boolean.TRUE;
                    double terminationDay = getTerminationDay(rentalRecordPo.getRentalEndTime());
                    log.debug("[处理时间:{}]-[设备编号:{}]-设备已退租,退租时间:{},退租当天计租天数:{}", DateUtil.now(), rentalRecordPo.getDeviceNo(), rentalRecordPo.getRentalEndTime(), terminationDay);

                    //======== 计算租金比例（退场10:00之前不算租金，10:00-15:30算0.5天租金，15:30之后算1天） - 开始 ==========
                    rate = terminationDay;
                    rentalRecordPo.setTotalRentalDays(rentalRecordPo.getTotalRentalDays() + terminationDay);
                    rentalRecordPo.setTotalLeaseDays(rentalRecordPo.getTotalLeaseDays() + 1);
                    //======== 计算租金比例（退场10:00之前不算租金，10:00-15:30算0.5天租金，15:30之后算1天） - 结束 ==========
                    //如果设备总租赁天数小于1天，则默认为1天
                    if (rentalRecordPo.getTotalRentalDays() < 1) {
                        rate = 1 - rentalRecordPo.getTotalRentalDays();
                        rentalRecordPo.setTotalRentalDays(1d);
                    }
                } else {
//                    isTermination = Boolean.TRUE;
                    //已退租，不产生租金
                    log.debug("[处理时间:{}]-[设备编号:{}]-已退租，不产生租金", DateUtil.now(), rentalRecordPo.getDeviceNo());
                    rate = 0;
                }
            }
        }


        //======== 计算已租天数 - 结束 ==========

        //======= 处理设备日转月 - 开始 ==========

        //查询属于当前租赁记录且为报停记录。费用时间等于今日的报停记录
        List<DeviceProfitLossDetailPo> pauseDetailList = this.deviceProfitLossDetailMapper.selectList(Wrappers.<DeviceProfitLossDetailPo>lambdaQuery().eq(DeviceProfitLossDetailPo::getDeviceRentalRecordId, rentalRecordPo.getId()).eq(DeviceProfitLossDetailPo::getBusinessBillType, BusinessBillTypeEnum.PAUSE).eq(DeviceProfitLossDetailPo::getFeeType, FeeTypeEnum.PAUSE_FEE).eq(DeviceProfitLossDetailPo::getStatus, ProfitLossStatusEnum.WAIT_EFFECTIVE).eq(DeviceProfitLossDetailPo::getIsDeleted, Boolean.FALSE).between(DeviceProfitLossDetailPo::getFeeIncurredTime, dto.getHandleDate().atStartOfDay(), dto.getHandleDate().atTime(LocalTime.MAX)));
        if (!pauseDetailList.isEmpty()) {
            DeviceProfitLossDetailPo pauseDetail = pauseDetailList.get(0);
            if (pauseDetail.getDays() >= rate) {
                rate = 0;
            } else {
                rate = rate - pauseDetail.getDays();
            }
            isPauseDay = Boolean.TRUE;
            rentalRecordPo.setTotalRentalDays(rentalRecordPo.getTotalRentalDays() - pauseDetail.getDays());
            rentalRecordPo.setTotalLeaseDays(rentalRecordPo.getTotalLeaseDays() - pauseDetail.getDays());
//            rentalRecordPo.setTotalRemainingPauseDays(rentalRecordPo.getTotalRemainingPauseDays() - pauseDetail.getFee().doubleValue());
            log.debug("[处理时间:{}]-[设备编号:{}]-[今日{}报停]设备报停天数：{}", DateUtil.now(), rentalRecordPo.getDeviceNo(), dto.getHandleDate(), rate);
        }
        //判断总租赁天数的租赁价格是否月租价格>日租价格
        BigDecimal monthlyRentalAmount = rentalRecordPo.getMonthlyRentalPrice();
        BigDecimal dailyRentalAmount = rentalRecordPo.getDailyRentalPrice().multiply(BigDecimal.valueOf(rentalRecordPo.getTotalRentalDays()));
        DeviceProfitLossDetailPo generateNonFullDayProfitLossResult = null;
        //判断是否已经使用月租金价格
        if (!rentalRecordPo.getUseMonthlyRentalPrice()) {
            log.debug("[处理时间:{}]-[设备编号:{}]-设备未使用月租金价格", DateUtil.now(), rentalRecordPo.getDeviceNo());
            if (monthlyRentalAmount.compareTo(dailyRentalAmount) <= 0) {
                //总租赁天数的租赁价格是月租价格>日租价格
                log.debug("[处理时间:{}]-[设备编号:{}]-[执行日转月]设备总租赁天数的租赁价格是月租价格>日租价格，月租总价:{},日租总价:{}", DateUtil.now(), rentalRecordPo.getDeviceNo(), monthlyRentalAmount, dailyRentalAmount);
                //设置已使用月租金价格
                rentalRecordPo.setUseMonthlyRentalPrice(true);
                //设置租赁单价为月租单价
                rentalPrice = rentalRecordPo.getMonthlyRentalPrice().divide(BigDecimal.valueOf(30), 5, RoundingMode.HALF_UP);
                /*
                发生日转月时，需要判断起租时间当天是否%1==0，如果不是，则需要扣减多计算的租金费用
                 */
                if (FinanceCalculateUtil.getRentalStartDay(rentalRecordPo.getRentalStartTime()) % 1 != 0) {
                    double diffDays = 1 - FinanceCalculateUtil.getRentalStartDay(rentalRecordPo.getRentalStartTime());
                    log.debug("[处理时间:{}]-[设备编号:{}]-[执行日转月]起租时间当天计租天数%1!=0，需要扣减多计算的{}天租金费用", DateUtil.now(), rentalRecordPo.getDeviceNo(), diffDays);
                    generateNonFullDayProfitLossResult = generateNonFullDayRentalProfitLoss(FeeTypeEnum.INITIAL_DAY_FEE_WAIVER, rentalRecordPo.getMonthlyRentalPrice().multiply(BigDecimal.valueOf(diffDays)).divide(BigDecimal.valueOf(30), 5, RoundingMode.HALF_UP), rentalRecordPo, dto.getHandleDate(), ProfitLossStatusEnum.WAIT_EFFECTIVE);
                    log.debug("[处理时间:{}]-[设备编号:{}]-[执行日转月]扣减多计算的{}天租金费用结果:{}", DateUtil.now(), rentalRecordPo.getDeviceNo(), diffDays, generateNonFullDayProfitLossResult);
                }
            }
        } else {
            log.debug("[处理时间:{}]-[设备编号:{}]-设备已使用月租金价格", DateUtil.now(), rentalRecordPo.getDeviceNo());
            rentalPrice = rentalRecordPo.getMonthlyRentalPrice().divide(BigDecimal.valueOf(30), 5, RoundingMode.HALF_UP);

        }
        //======= 处理设备日转月 - 结束 ==========
        //======= 处理设备日流水 - 开始 ==========
        DeviceDailyBillPo deviceDailyBill = this.generateDeviceDailyBillPo(rentalRecordPo, rate, rentalPrice, dto.getHandleDate().atTime(LocalTime.MAX).minusSeconds(5), isTermination, generateNonFullDayProfitLossResult);
        deviceDailyBill.setPauseFlag(isPauseDay);
        //======= 处理设备日流水 - 结束 ==========
        //宽限期
        int gracePeriod = dto.getOrderExtensionInfoJson().getAccountPeriodDays();
        //如果为先付款，宽限期为5
        if (dto.getOrderExtensionInfoJson().getAccountPeriod().equals(1)) {
            gracePeriod = 5;
        }
        Map<String, DailyInvoiceGenerationJobDto.PeriodInterval> accountPeriodIntervalMap = new HashMap<>();
        generateDeviceOverdueInfo(dto.getOrderExtensionInfoJson().getSettlementMethod(), dto.getOrderExtensionInfoJson().getAccountPeriod(), rentalRecordPo, accountPeriodIntervalMap, gracePeriod);
        log.debug("[处理时间:{}]-[设备编号:{}]-账龄信息是:{}", DateUtil.now(), rentalRecordPo.getDeviceNo(), JSONUtil.toJsonStr(accountPeriodIntervalMap));
        List<OverdueDetailJson> overdueDetailList = new ArrayList<>();
        //遍历accountPeriodIntervalMap的entry
        accountPeriodIntervalMap.forEach((key, value) -> {
            //逾期账期
            OverdueDetailJson overdueDetailDto = new OverdueDetailJson();
            overdueDetailDto.setOverdueDaysDesc(key);
            if (value != null) {

                overdueDetailDto.setOverdueStartDate(value.getStart());
                overdueDetailDto.setOverdueEndDate(value.getEnd());
                if (value.getOverdueRule() != null) {
                    overdueDetailDto.setOverdueDaysStart(value.getOverdueRule().getStartDays());
                    overdueDetailDto.setOverdueDaysEnd(value.getOverdueRule().getEndDays());
                    overdueDetailDto.setOverdueDaysSort(value.getOverdueRule().getRuleSort());
                    overdueDetailDto.setOverdueDaysKey(value.getOverdueRule().getRuleKey());
                }
                //根据value中的start和end 查询当前设备ID所有费用信息（月租价格、日租价格、总损益）
                OverdueAmountResultPo overdueAmount = this.deviceRentalRecordMapper.selectDeviceOverdueAmount(rentalRecordPo.getId(), value.getStart() != null ? value.getStart().atStartOfDay() : null, value.getEnd() != null ? value.getEnd().atTime(LocalTime.MAX) : null);

                //判断设备租赁记录是否已使用月租单价
                overdueDetailDto.setOverdueAmount(overdueAmount.getRentalAmount());
                //判断dto的handleDate是否处于value的start与end之间
                if ((value.getStart() != null && (dto.getHandleDate().isAfter(value.getStart())) && dto.getHandleDate().isBefore(value.getEnd()))) {
                    //如果是，则将overdueDetailDto的overdueAmount累加上今日的费用
                    overdueDetailDto.setOverdueAmount(overdueDetailDto.getOverdueAmount().add(deviceDailyBill.getUnpaidAmount()));
                }
            } else {
                overdueDetailDto.setOverdueAmount(BigDecimal.ZERO);
            }
            overdueDetailList.add(overdueDetailDto);
        });
        rentalRecordPo.setOverdueInfoList(overdueDetailList);
        rentalRecordPo.setBillingThroughTime(dto.getHandleDate().atTime(LocalTime.MAX).minusSeconds(5));
        log.debug("[处理时间:{}]-[设备编号:{}]-生成设备每日费用流水,结束", DateUtil.now(), rentalRecordPo.getDeviceNo());
        //设置客户以及订单信息
        dto.setCustomerExtensionInfoJson(rentalRecordPo.getCustomerExtensionInfo());

        deviceRentalRecordList.set(i, rentalRecordPo);
        return deviceDailyBill;
    }

    /**
     * 生成设备的账龄信息
     *
     * @param rentalRecordPo           设备租赁记录
     * @param accountPeriodIntervalMap 账期区间
     * @param gracePeriod              宽限期
     * @author songkf
     */
    private static void generateDeviceOverdueInfo(Integer settlementMethod, Integer accountPeriod, DeviceRentalRecordPo
            rentalRecordPo, Map<String, DailyInvoiceGenerationJobDto.PeriodInterval> accountPeriodIntervalMap,
                                                  int gracePeriod) {
        LocalDate unexpiredPeriodStart;
        LocalDate unexpiredPeriodEnd;
        LocalDate prevPeriodStart;
        LocalDate prevPeriodEnd;
        LocalDate firstOverdueStart;
        LocalDate firstOverdueEnd;
        LocalDate secondOverdueStart;
        LocalDate secondOverdueEnd;
        LocalDate thirdOverdueStart;
        LocalDate thirdOverdueEnd;
        LocalDate fourthOverdueStart;
        LocalDate fourthOverdueEnd;
        if (settlementMethod.equals(10)) {
            //如果是月结
            unexpiredPeriodStart = LocalDate.now().withDayOfMonth(1);
            //如果unexpiredPeriodStart在rentalRecordPo的rentalStartDate之前，则unexpiredPeriodStart设置为rentalRecordPo的rentalStartDate的00:00:00
            if (unexpiredPeriodStart.isBefore(rentalRecordPo.getRentalStartTime().toLocalDate())) {
                unexpiredPeriodStart = rentalRecordPo.getRentalStartTime().toLocalDate();
            }
            unexpiredPeriodEnd = LocalDate.now().withDayOfMonth(unexpiredPeriodStart.lengthOfMonth());
            //如果当前时间处于当前月1号-当前月1号+宽限期，则上个月的账期为当前账期，否则为逾期0-30天
            prevPeriodStart = LocalDate.now().minusMonths(1).withDayOfMonth(1);
            prevPeriodEnd = prevPeriodStart.withDayOfMonth(prevPeriodStart.lengthOfMonth());
            firstOverdueStart = prevPeriodStart.minusMonths(1).withDayOfMonth(1);
            firstOverdueEnd = firstOverdueStart.withDayOfMonth(firstOverdueStart.lengthOfMonth());
            secondOverdueStart = firstOverdueStart.minusMonths(1).withDayOfMonth(1);
            secondOverdueEnd = secondOverdueStart.withDayOfMonth(secondOverdueStart.lengthOfMonth());
            thirdOverdueStart = secondOverdueStart.minusMonths(1).withDayOfMonth(1);
            thirdOverdueEnd = thirdOverdueStart.withDayOfMonth(thirdOverdueStart.lengthOfMonth());
            fourthOverdueStart = thirdOverdueStart.minusMonths(1).withDayOfMonth(1);
            fourthOverdueEnd = fourthOverdueStart.withDayOfMonth(fourthOverdueStart.lengthOfMonth());
        } else {
            //如果是期结
            //计算当前日期与起租时间之间的账期差,30个自然日为一个账期
            // 设定账期的天数
            int daysPerBillingCycle = 30;
            //计算当前日期与起租时间之间的自然日差
            // 计算自然日差
            long daysDiff = ChronoUnit.DAYS.between(rentalRecordPo.getRentalStartTime().toLocalDate(), LocalDate.now());
            // 计算账期余数
            long remainder = daysDiff % daysPerBillingCycle;
            //今日所属账期开始时间为：当前时间往前推remainder天
            unexpiredPeriodStart = LocalDate.now().minusDays(remainder);
            //今日所属账期结束时间为：当前时间往前推remainder天+30天
            unexpiredPeriodEnd = unexpiredPeriodStart.plusDays(daysPerBillingCycle - 1);
            prevPeriodStart = unexpiredPeriodStart.minusDays(daysPerBillingCycle);
            prevPeriodEnd = prevPeriodStart.plusDays(daysPerBillingCycle - 1);
            firstOverdueStart = prevPeriodStart.minusDays(daysPerBillingCycle);
            firstOverdueEnd = firstOverdueStart.plusDays(daysPerBillingCycle - 1);
            secondOverdueStart = firstOverdueStart.minusDays(daysPerBillingCycle);
            secondOverdueEnd = secondOverdueStart.plusDays(daysPerBillingCycle - 1);
            thirdOverdueStart = secondOverdueStart.minusDays(daysPerBillingCycle);
            thirdOverdueEnd = thirdOverdueStart.plusDays(daysPerBillingCycle - 1);
            fourthOverdueStart = thirdOverdueStart.minusDays(daysPerBillingCycle);
            fourthOverdueEnd = fourthOverdueStart.plusDays(daysPerBillingCycle - 1);

        }
        if (accountPeriod.equals(1)) {
            accountPeriodIntervalMap.put(CommonConstants.UNEXPIRED_PERIOD, null);
            if (!LocalDate.now().isBefore(unexpiredPeriodStart) && !LocalDate.now().isAfter(unexpiredPeriodStart.plusDays(gracePeriod))) {
                accountPeriodIntervalMap.put(CommonConstants.CURRENT_PERIOD, new DailyInvoiceGenerationJobDto.PeriodInterval(unexpiredPeriodStart, unexpiredPeriodEnd, new ReceivableInventoryOverdueRule.Rule("到期", "CURRENT", 50, null, null)));
                accountPeriodIntervalMap.put("逾期0-30天", new DailyInvoiceGenerationJobDto.PeriodInterval(prevPeriodStart, prevPeriodEnd, new ReceivableInventoryOverdueRule.Rule("逾期0-30天", "OVERDUE_0_30", 40, 0, 30)));
                accountPeriodIntervalMap.put("逾期31-60天", new DailyInvoiceGenerationJobDto.PeriodInterval(firstOverdueStart, firstOverdueEnd, new ReceivableInventoryOverdueRule.Rule("逾期31-60天", "OVERDUE_31_60", 30, 31, 60)));
                accountPeriodIntervalMap.put("逾期61-90天", new DailyInvoiceGenerationJobDto.PeriodInterval(secondOverdueStart, secondOverdueEnd, new ReceivableInventoryOverdueRule.Rule("逾期61-90天", "OVERDUE_61_90", 20, 61, 90)));
                accountPeriodIntervalMap.put("逾期91天以上", new DailyInvoiceGenerationJobDto.PeriodInterval(null, thirdOverdueEnd, new ReceivableInventoryOverdueRule.Rule("逾期91天以上", "OVERDUE_90", 10, 91, null)));
            } else {
                accountPeriodIntervalMap.put(CommonConstants.CURRENT_PERIOD, null);
                accountPeriodIntervalMap.put("逾期0-30天", new DailyInvoiceGenerationJobDto.PeriodInterval(unexpiredPeriodStart, unexpiredPeriodEnd, new ReceivableInventoryOverdueRule.Rule("逾期0-30天", "OVERDUE_0_30", 40, 0, 30)));
                accountPeriodIntervalMap.put("逾期31-60天", new DailyInvoiceGenerationJobDto.PeriodInterval(prevPeriodStart, prevPeriodEnd, new ReceivableInventoryOverdueRule.Rule("逾期31-60天", "OVERDUE_31_60", 30, 31, 60)));
                accountPeriodIntervalMap.put("逾期61-90天", new DailyInvoiceGenerationJobDto.PeriodInterval(firstOverdueStart, firstOverdueEnd, new ReceivableInventoryOverdueRule.Rule("逾期61-90天", "OVERDUE_61_90", 20, 61, 90)));
                accountPeriodIntervalMap.put("逾期91天以上", new DailyInvoiceGenerationJobDto.PeriodInterval(null, secondOverdueEnd, new ReceivableInventoryOverdueRule.Rule("逾期91天以上", "OVERDUE_90", 10, 91, null)));
            }
        } else {
            //每个账期区间都是一整月，当前日期所在的月份均属于未到期
            accountPeriodIntervalMap.put(CommonConstants.UNEXPIRED_PERIOD, new DailyInvoiceGenerationJobDto.PeriodInterval(unexpiredPeriodStart, unexpiredPeriodEnd, new ReceivableInventoryOverdueRule.Rule("未到期", "UNEXPIRED", 90, null, null)));
            if (!LocalDate.now().isBefore(unexpiredPeriodStart) && !LocalDate.now().isAfter(unexpiredPeriodStart.plusDays(gracePeriod))) {
                accountPeriodIntervalMap.put(CommonConstants.CURRENT_PERIOD, new DailyInvoiceGenerationJobDto.PeriodInterval(prevPeriodStart, prevPeriodEnd, new ReceivableInventoryOverdueRule.Rule("到期", "CURRENT", 50, null, null)));
                accountPeriodIntervalMap.put("逾期0-30天", new DailyInvoiceGenerationJobDto.PeriodInterval(firstOverdueStart, firstOverdueEnd, new ReceivableInventoryOverdueRule.Rule("逾期0-30天", "OVERDUE_0_30", 40, 0, 30)));
                accountPeriodIntervalMap.put("逾期31-60天", new DailyInvoiceGenerationJobDto.PeriodInterval(secondOverdueStart, secondOverdueEnd, new ReceivableInventoryOverdueRule.Rule("逾期31-60天", "OVERDUE_31_60", 30, 31, 60)));
                accountPeriodIntervalMap.put("逾期61-90天", new DailyInvoiceGenerationJobDto.PeriodInterval(thirdOverdueStart, thirdOverdueEnd, new ReceivableInventoryOverdueRule.Rule("逾期61-90天", "OVERDUE_61_90", 20, 61, 90)));
                accountPeriodIntervalMap.put("逾期91天以上", new DailyInvoiceGenerationJobDto.PeriodInterval(null, fourthOverdueEnd, new ReceivableInventoryOverdueRule.Rule("逾期91天以上", "OVERDUE_90", 10, 91, null)));
            } else {
                accountPeriodIntervalMap.put(CommonConstants.CURRENT_PERIOD, null);
                accountPeriodIntervalMap.put("逾期0-30天", new DailyInvoiceGenerationJobDto.PeriodInterval(prevPeriodStart, prevPeriodEnd, new ReceivableInventoryOverdueRule.Rule("逾期0-30天", "OVERDUE_0_30", 40, 0, 30)));
                accountPeriodIntervalMap.put("逾期31-60天", new DailyInvoiceGenerationJobDto.PeriodInterval(firstOverdueStart, firstOverdueEnd, new ReceivableInventoryOverdueRule.Rule("逾期31-60天", "OVERDUE_31_60", 30, 31, 60)));
                accountPeriodIntervalMap.put("逾期61-90天", new DailyInvoiceGenerationJobDto.PeriodInterval(secondOverdueStart, secondOverdueEnd, new ReceivableInventoryOverdueRule.Rule("逾期61-90天", "OVERDUE_61_90", 20, 61, 90)));
                accountPeriodIntervalMap.put("逾期91天以上", new DailyInvoiceGenerationJobDto.PeriodInterval(null, thirdOverdueEnd, new ReceivableInventoryOverdueRule.Rule("逾期91天以上", "OVERDUE_90", 10, 91, null)));
            }
        }
    }

    /**
     * 计算退场天数
     *
     * @param rentalEndTime 租赁结束时间
     * @return 退场天数
     * @author songkf
     */
    private static double getTerminationDay(LocalDateTime rentalEndTime) {
        double terminationDay;
        log.debug("[处理时间:{}]-计算退场天数,日期时间:{},本地时间:{}", DateUtil.now(), rentalEndTime, rentalEndTime.toLocalTime());
        //计算租赁天数，如果rentalEndTime在当天10点前，则计租0天；在当天10点到15：30之间，计租0.5天；当前15：30之后，计租1天
        if (rentalEndTime.toLocalTime().isBefore(TEN_AM.plusSeconds(1))) {
            terminationDay = 0d;
        } else if (rentalEndTime.toLocalTime().isAfter(TEN_AM) && rentalEndTime.toLocalTime().isBefore(THREE_THIRTY_PM.plusSeconds(1))) {
            terminationDay = 0.5d;
        } else {
            terminationDay = 1d;
        }
        return terminationDay;
    }

    private static double getTerminationDayStart(LocalDateTime rentalEndTime) {
        double terminationDay;
        log.debug("[处理时间:{}]-计算退场天数,日期时间:{},本地时间:{}", DateUtil.now(), rentalEndTime, rentalEndTime.toLocalTime());
        //计算租赁天数，如果rentalEndTime在当天10点前，则计租0天；在当天10点到15：30之间，计租0.5天；当前15：30之后，计租1天
        if (rentalEndTime.toLocalTime().isBefore(TEN_AM)) {
            terminationDay = 1d;
        } else if (rentalEndTime.toLocalTime().isAfter(TEN_AM.minusSeconds(1)) && rentalEndTime.toLocalTime().isBefore(THREE_THIRTY_PM.minusSeconds(1))) {
            terminationDay = 0.5d;
        } else {
            terminationDay = 0d;
        }
        return terminationDay;
    }


    /**
     * 生成设备每日费用流水
     *
     * @param deviceRentalRecordPo 设备租赁记录
     * @param isTermination
     * @return 设备每日费用流水
     * @author songkf
     */
    private DeviceDailyBillPo generateDeviceDailyBillPo(DeviceRentalRecordPo deviceRentalRecordPo, Double rate, BigDecimal rentalPrice, LocalDateTime feeIncurredTime, Boolean isTermination, DeviceProfitLossDetailPo generateNonFullDayProfitLossResult) {
        DeviceDailyBillPo deviceDailyBill = new DeviceDailyBillPo();
        deviceDailyBill.setDeviceRentalRecordId(deviceRentalRecordPo.getId());
        deviceDailyBill.setDeviceId(deviceRentalRecordPo.getDeviceId());
        deviceDailyBill.setContractId(deviceRentalRecordPo.getContractId());
        deviceDailyBill.setContractNo(deviceRentalRecordPo.getContractNo());
        deviceDailyBill.setCustomerId(deviceRentalRecordPo.getCustomerId());
        deviceDailyBill.setEntryFormId(deviceRentalRecordPo.getEntryFormId());
        deviceDailyBill.setDeviceNo(deviceRentalRecordPo.getDeviceNo());
        deviceDailyBill.setFeeStatus(Boolean.TRUE);
        //今日计费天数
        deviceDailyBill.setChargeDays(rate);
        deviceDailyBill.setDailyFee(deviceRentalRecordPo.getDailyRentalPrice().multiply(BigDecimal.valueOf(rate)));
        deviceDailyBill.setMonthlyFee(deviceRentalRecordPo.getMonthlyRentalPrice().multiply(BigDecimal.valueOf(rate)).divide(BigDecimal.valueOf(30), 5, RoundingMode.HALF_UP));
        //2.2 根据deviceDailyBillPo的id查询设备损益明细记录集合
        List<DeviceProfitLossDetailPo> deviceProfitLossDetailPoList = this.deviceProfitLossDetailService.queryTodayDeviceProfitLossDetailByDeviceId(deviceRentalRecordPo.getId(), feeIncurredTime.toLocalDate());
        if (generateNonFullDayProfitLossResult != null) {
            deviceProfitLossDetailPoList.add(generateNonFullDayProfitLossResult);
            log.info("[处理时间:{}]-[设备编号:{}]-[执行日转月]-增加了入场首日0.5天损益.损益类型是:{}", DateUtil.now(), deviceRentalRecordPo.getDeviceNo(), generateNonFullDayProfitLossResult);
        }
        //如果已退租，起租当天计租天数%1！=0，已使用月租金，且总租金费用小于月租金费用
        if (isTermination &&
                ((
                        deviceRentalRecordPo.getUseMonthlyRentalPrice() &&
                                FinanceCalculateUtil.getRentalStartDay(deviceRentalRecordPo.getRentalStartTime()) % 1 != 0 && deviceProfitLossDetailService.checkInitialDayFeeWaiver(deviceRentalRecordPo.getId()) &&
                                deviceRentalRecordPo.getTotalRentalAmount().compareTo(deviceRentalRecordPo.getMonthlyRentalPrice()) <= 0)
//                || (FinanceCalculateUtil.getRentalStartDay(deviceRentalRecordPo.getRentalStartTime()) % 1 != 0 && deviceRentalRecordPo.getTotalRentalAmount().compareTo(BigDecimal.ZERO) >= 0 && deviceRentalRecordPo.getTotalRentalAmount().compareTo(deviceRentalRecordPo.getDailyRentalPrice()) < 0)
                )) {
            double diffDays = 1 - FinanceCalculateUtil.getRentalStartDay(deviceRentalRecordPo.getRentalStartTime());
            log.info("[处理时间:{}]-[设备编号:{}]-[执行日转月]起租时间当天计租天数%1!=0，需要扣减多计算的{}天租金费用", DateUtil.now(), deviceRentalRecordPo.getDeviceNo(), diffDays);
            BigDecimal diffAmount = (deviceRentalRecordPo.getMonthlyRentalPrice().multiply(BigDecimal.valueOf(diffDays))).divide(BigDecimal.valueOf(30), 5, RoundingMode.HALF_UP);//deviceRentalRecordPo.getMonthlyRentalPrice().subtract(deviceRentalRecordPo.getTotalRentalAmount());
            DeviceProfitLossDetailPo generateNonFullDayProfitLossResult1 = generateNonFullDayRentalProfitLoss(FeeTypeEnum.INITIAL_DAY_FEE, diffAmount, deviceRentalRecordPo, feeIncurredTime.toLocalDate(), ProfitLossStatusEnum.WAIT_EFFECTIVE);
            log.info("[处理时间:{}]-[设备编号:{}]-[执行日转月]补足月租金差额:{}", DateUtil.now(), deviceRentalRecordPo.getDeviceNo(), diffAmount);
            deviceProfitLossDetailPoList.add(generateNonFullDayProfitLossResult1);
        }
        //触发运费减免、改装减免
        List<DeviceProfitLossDetailPo> waiverProfitLossList = initializeWaiverProfitLoss(deviceRentalRecordPo, feeIncurredTime.toLocalDate());
        if (!CollectionUtil.isEmpty(waiverProfitLossList)) {
            log.info("[处理时间:{}]-[设备编号:{}]-[触发运费减免、改装减免]-减免集合信息:{}", feeIncurredTime.toLocalDate(), deviceRentalRecordPo.getDeviceNo(), JSONUtil.toJsonStr(waiverProfitLossList));
            deviceProfitLossDetailPoList.addAll(waiverProfitLossList);
        }
        //遍历deviceProfitLossDetailPoList，计算损益总金额，并记录损益明细ProfitLossInfoJson
        BigDecimal totalProfitLossAmount = BigDecimal.ZERO;
        List<ProfitLossInfoJson> profitLossInfoJson = new ArrayList<>();
        log.info("[处理时间:{}]-[设备编号:{}]-[执行日转月]-计算损益总金额开始,损益集合信息:{}", DateUtil.now(), deviceRentalRecordPo.getDeviceNo(), JSONUtil.toJsonStr(deviceProfitLossDetailPoList));
        getProfitLossInfoJsons(profitLossInfoJson, totalProfitLossAmount, deviceProfitLossDetailPoList, deviceRentalRecordPo);
        BigDecimal monthlyAmountThreshold = calculateFirstPeriodMonthlyAmountThreshold(deviceRentalRecordPo);
//        BigDecimal monthlyRentalAmount = deviceRentalRecordPo.getTotalRentalDays() > 30 ? deviceRentalRecordPo.getMonthlyRentalPrice() : deviceRentalRecordPo.getMonthlyRentalPrice().multiply(BigDecimal.valueOf(30 - 1 + FinanceCalculateUtil.getRentalStartDay(deviceRentalRecordPo.getRentalStartTime()))).divide(BigDecimal.valueOf(30), 5, RoundingMode.HALF_UP);
        log.info("[处理时间:{}]-[设备编号:{}]-[本月最大租金阈值:{}]", DateUtil.now(), deviceRentalRecordPo.getDeviceNo(), monthlyAmountThreshold);
        BigDecimal dailyRentalAmount = deviceRentalRecordPo.getDailyRentalPrice().multiply(BigDecimal.valueOf(deviceRentalRecordPo.getTotalRentalDays()));
        if (deviceRentalRecordPo.getTotalRentalDays() > 30) {
            deviceDailyBill.setFee((deviceRentalRecordPo.getMonthlyRentalPrice().multiply(BigDecimal.valueOf(rate))).divide(BigDecimal.valueOf(30), 5, RoundingMode.HALF_UP));
        } else if (monthlyAmountThreshold.compareTo(dailyRentalAmount) <= 0) {
            //将fee设置为月租价格-日租价+今日的日租单价
            if (monthlyAmountThreshold.compareTo(deviceRentalRecordPo.getTotalRentalAmount()) > 0) {
                double diffDays = deviceRentalRecordPo.getTotalPauseDays() % 1;
                BigDecimal diffAmount = BigDecimal.ZERO;
                if (diffDays > 0) {
                    diffAmount = (deviceRentalRecordPo.getMonthlyRentalPrice().multiply(BigDecimal.valueOf(diffDays))).divide(BigDecimal.valueOf(30), 5, RoundingMode.HALF_UP);
                }
                deviceDailyBill.setFee(deviceRentalRecordPo.getMonthlyRentalPrice().subtract(diffAmount).subtract(deviceRentalRecordPo.getTotalRentalAmount()));
            } else if (isTermination) {
                BigDecimal diffAmount = BigDecimal.ZERO;
                double diffDays = 0d;
                if (FinanceCalculateUtil.getRentalStartDay(deviceRentalRecordPo.getRentalStartTime()) % 1 != 0) {
                    diffDays = 1 - FinanceCalculateUtil.getRentalStartDay(deviceRentalRecordPo.getRentalStartTime());
                    diffAmount = (deviceRentalRecordPo.getMonthlyRentalPrice().multiply(BigDecimal.valueOf(diffDays))).divide(BigDecimal.valueOf(30), 5, RoundingMode.HALF_UP);//deviceRentalRecordPo.getMonthlyRentalPrice().subtract(deviceRentalRecordPo.getTotalRentalAmount());
                }
                log.info("[处理时间:{}]-[设备编号:{}]-[b补足月租金差额]起租时间当天计租天数{}}，扣减多计算的{}天租金费用.目前总租金是:{}", DateUtil.formatLocalDateTime(feeIncurredTime), deviceRentalRecordPo.getDeviceNo(), diffDays, diffAmount, deviceRentalRecordPo.getTotalRentalAmount());
                deviceDailyBill.setFee(deviceRentalRecordPo.getMonthlyRentalPrice().subtract(deviceRentalRecordPo.getTotalRentalAmount()).subtract(diffAmount));
            } else if (deviceRentalRecordPo.getRentalEndTime() == null || feeIncurredTime.toLocalDate().isBefore(deviceRentalRecordPo.getRentalEndTime().toLocalDate())) {
                deviceDailyBill.setFee((deviceRentalRecordPo.getMonthlyRentalPrice().multiply(BigDecimal.valueOf(rate - 1))).divide(BigDecimal.valueOf(30), 5, RoundingMode.HALF_UP));
            } else {
                deviceDailyBill.setFee(BigDecimal.ZERO);
            }
        } else {
            deviceDailyBill.setFee(rentalPrice.multiply(BigDecimal.valueOf(rate)));
        }
        AtomicReference<BigDecimal> needSubtractAmount = new AtomicReference<>(BigDecimal.ZERO);
        //遍历profitLossInfoJson，计算损益总金额,如果是WAIVER_FEE_TYPE_LIST中的，就执行减法，否则执行加法
        totalProfitLossAmount = profitLossInfoJson.stream().map(item -> {
            if (Objects.equals(FeeTypeEnum.getFeeTypeEnumByValue(item.getType()), FeeTypeEnum.INITIAL_DAY_FEE_WAIVER)) {
                needSubtractAmount.set(needSubtractAmount.get().add(item.getFee().negate()));

            } else if (Objects.equals(FeeTypeEnum.getFeeTypeEnumByValue(item.getType()), FeeTypeEnum.INITIAL_DAY_FEE)) {
                needSubtractAmount.set(needSubtractAmount.get().add(item.getFee()));
            }
            if (WAIVER_FEE_TYPE_LIST.contains(FeeTypeEnum.getFeeTypeEnumByValue(item.getType()))) {
                return item.getFee().negate();
            }
            return item.getFee();
        }).reduce(BigDecimal.ZERO, BigDecimal::add);
        deviceDailyBill.setProfitLossInfo(profitLossInfoJson);
        deviceDailyBill.setTotalProfitLossAmount(totalProfitLossAmount);
        deviceDailyBill.setPaymentStatus(PaymentStatusEnum.UNPAID);
        deviceDailyBill.setUnpaidAmount(deviceDailyBill.getFee().add(deviceDailyBill.getTotalProfitLossAmount()));
        deviceDailyBill.setPaidAmount(BigDecimal.ZERO);
        deviceDailyBill.setFeeIncurredTime(feeIncurredTime);
        if (feeIncurredTime.toLocalDate().equals(deviceRentalRecordPo.getRentalStartTime().toLocalDate())) {
            deviceDailyBill.setType(1);
        } else if (deviceRentalRecordPo.getRentalEndTime() != null && feeIncurredTime.toLocalDate().equals(deviceRentalRecordPo.getRentalEndTime().toLocalDate())) {
            deviceDailyBill.setType(9);
        } else {
            deviceDailyBill.setType(0);
        }
        deviceDailyBill.setId(identifierGenerator.nextId(deviceDailyBill));
        deviceRentalRecordPo.setTotalProfitLossAmount(deviceRentalRecordPo.getTotalProfitLossAmount().add(totalProfitLossAmount).subtract(needSubtractAmount.get()));
        //计算总租金,总租金=历史总租金+今日租金费用
        BigDecimal totalRentalAmount = deviceRentalRecordPo.getTotalRentalAmount().add(deviceDailyBill.getFee());
        log.error("[处理日期:{}]-[设备编号:{}]-设备历史总租金为:{}-今日租金费用为:{}-总租金为:{}", DateUtil.formatLocalDateTime(feeIncurredTime), deviceRentalRecordPo.getDeviceNo(), deviceRentalRecordPo.getTotalRentalAmount(), deviceDailyBill.getFee(), totalRentalAmount);
        //计算报停费用
        BigDecimal totalPauseAmount = BigDecimal.valueOf(deviceRentalRecordPo.getTotalPauseDays()).multiply(rentalPrice);
        //总营收=（总租金-总报停费用）+总损益金额
        BigDecimal totalRevenueAmount = totalRentalAmount.add(deviceRentalRecordPo.getTotalProfitLossAmount());

        //总租金-非全天租金费用
        for (DeviceProfitLossDetailPo profitLossDetailPo : deviceProfitLossDetailPoList) {
            if (profitLossDetailPo.getFeeType().equals(FeeTypeEnum.INITIAL_DAY_FEE)) {
                totalRentalAmount = totalRentalAmount.add(profitLossDetailPo.getFee());
            } else if (profitLossDetailPo.getFeeType().equals(FeeTypeEnum.INITIAL_DAY_FEE_WAIVER)) {
                totalRentalAmount = totalRentalAmount.subtract(profitLossDetailPo.getFee());
            }
        }
        //设置设备设备租赁记录的费用信息
        deviceRentalRecordPo.setTotalRentalAmount(totalRentalAmount);
        deviceRentalRecordPo.setTotalRevenueAmount(totalRevenueAmount);
        //未对账金额=总营收-已对账金额
        BigDecimal totalUnreconciledAmount = deviceRentalRecordPo.getTotalRevenueAmount().subtract(deviceRentalRecordPo.getTotalReconciledAmount());
        deviceRentalRecordPo.setTotalUnreconciledAmount(totalUnreconciledAmount);
        deviceDailyBill.setTotalRevenueAmount(deviceRentalRecordPo.getTotalRevenueAmount());
        deviceDailyBill.setTotalReconciledAmount(deviceRentalRecordPo.getTotalReconciledAmount());
        deviceDailyBill.setHistoryReconciledAmount(deviceRentalRecordPo.getHistoryReconciledAmount());
        deviceDailyBill.setTotalUnreconciledAmount(deviceRentalRecordPo.getTotalUnreconciledAmount());
        //批量更新deviceProfitLossDetailPoList关联的ProfitLossRecordPo的状态为已生效
        if (deviceProfitLossDetailPoList.isEmpty()) {
            return deviceDailyBill;
        }
        ProfitLossRecordPo updatePo = new ProfitLossRecordPo();
        updatePo.setStatus(ProfitLossStatusEnum.ALREADY_EFFECTIVE);
        profitLossRecordMapper.update(updatePo, Wrappers.<ProfitLossRecordPo>lambdaUpdate().in(ProfitLossRecordPo::getId, deviceProfitLossDetailPoList.stream().map(DeviceProfitLossDetailPo::getProfitLossRecordId).collect(Collectors.toSet())));
        //批量更新deviceRentalRecordPoList关联的ProfitLossRecordPo的状态为已生效
        DeviceProfitLossDetailPo updateRecordPo = new DeviceProfitLossDetailPo();
        updateRecordPo.setStatus(ProfitLossStatusEnum.ALREADY_EFFECTIVE);
        deviceProfitLossDetailMapper.update(updateRecordPo, Wrappers.<DeviceProfitLossDetailPo>lambdaUpdate().in(DeviceProfitLossDetailPo::getId, deviceProfitLossDetailPoList.stream().map(DeviceProfitLossDetailPo::getId).collect(Collectors.toSet())));
        return deviceDailyBill;
    }

    private BigDecimal calculateFirstPeriodMonthlyAmountThreshold(DeviceRentalRecordPo deviceRentalRecordPo) {
        //累计报停天数+首日计租天数 mod 1,如果等于0，则不需要减去首日计租费用
        double diffDays = (FinanceCalculateUtil.getRentalStartDay(deviceRentalRecordPo.getRentalStartTime()) + deviceRentalRecordPo.getTotalPauseDays()) % 1;
        if (diffDays == 0) {
            return deviceRentalRecordPo.getMonthlyRentalPrice();
        }
        return deviceRentalRecordPo.getMonthlyRentalPrice().multiply(BigDecimal.valueOf(30 - 1 + diffDays)).divide(BigDecimal.valueOf(30), 5, RoundingMode.HALF_UP);
    }

    private List<DeviceProfitLossDetailPo> initializeWaiverProfitLoss(DeviceRentalRecordPo deviceRentalRecordPo, LocalDate localDate) {
        List<DeviceProfitLossDetailPo> waiverProfitLossList = new ArrayList<>();
        //如果已超出退租时间，则不产生减免损益
        if (deviceRentalRecordPo.getRentalEndTime() != null && localDate.isAfter(deviceRentalRecordPo.getRentalEndTime().toLocalDate())) {
            return waiverProfitLossList;
        }
        //运费减免
        if (deviceRentalRecordPo.getEntryShippingInfo().getShippingWaiverRuleList() != null && !deviceRentalRecordPo.getEntryShippingInfo().getShippingWaiverRuleList().isEmpty()) {
            for (WaiverRuleJson shippingWaiverRuleJson : deviceRentalRecordPo.getEntryShippingInfo().getShippingWaiverRuleList()) {
                //如果设备租赁记录的计租天数等于运费减免规则的天数，则生成运费减免损益
                if (Math.floor(deviceRentalRecordPo.getTotalRentalDays()) == shippingWaiverRuleJson.getWaiverDays()) {
                    log.info("[处理时间:{}]-[设备编号:{}]-设备租赁记录的计租天数:{},等于运费减免规则的天数:{}", DateUtil.now(), deviceRentalRecordPo.getDeviceNo(), deviceRentalRecordPo.getTotalRentalDays(), shippingWaiverRuleJson.getWaiverDays());
                    DeviceProfitLossDetailPo deviceProfitLossDetailPo = new DeviceProfitLossDetailPo();
                    deviceProfitLossDetailPo.setBusinessBillType(BusinessBillTypeEnum.ENTRY);
                    deviceProfitLossDetailPo.setFeeType(FeeTypeEnum.SHIPPING_FEE_WAIVER);
                    deviceProfitLossDetailPo.setFee(deviceRentalRecordPo.getEntryShippingInfo().getShippingTotalAmount().multiply(BigDecimal.valueOf(shippingWaiverRuleJson.getWaiverRate())));
                    deviceProfitLossDetailPo.setProfitLossRecordId(null);
                    deviceProfitLossDetailPo.setStatus(ProfitLossStatusEnum.ALREADY_EFFECTIVE);
                    deviceProfitLossDetailPo.setDeviceId(deviceRentalRecordPo.getDeviceId());
                    deviceProfitLossDetailPo.setContractId(deviceRentalRecordPo.getContractId());
                    deviceProfitLossDetailPo.setContractNo(deviceRentalRecordPo.getContractNo());
                    deviceProfitLossDetailPo.setDeviceNo(deviceRentalRecordPo.getDeviceNo());
                    deviceProfitLossDetailPo.setDeviceRentalRecordId(deviceRentalRecordPo.getId());
                    deviceProfitLossDetailPo.setFeeIncurredTime(localDate.atStartOfDay());
                    deviceProfitLossDetailPo.setRemarks("系统生成运费减免损益");
                    deviceProfitLossDetailService.save(deviceProfitLossDetailPo);
                    waiverProfitLossList.add(deviceProfitLossDetailPo);
                }
            }
        }
        //改装费减免
        if (deviceRentalRecordPo.getModificationFeeInfo().getModificationWaiverRuleList() != null && !deviceRentalRecordPo.getModificationFeeInfo().getModificationWaiverRuleList().isEmpty()) {
            for (WaiverRuleJson modificationWaiverRuleJson : deviceRentalRecordPo.getModificationFeeInfo().getModificationWaiverRuleList()) {
                //如果设备租赁记录的计租天数等于改装费减免规则的天数，则生成改装费减免损益
                if (Math.floor(deviceRentalRecordPo.getTotalRentalDays()) == modificationWaiverRuleJson.getWaiverDays()) {
                    log.info("[处理时间:{}]-[设备编号:{}]-设备租赁记录的计租天数:{},等于改装费减免规则的天数:{}", DateUtil.now(), deviceRentalRecordPo.getDeviceNo(), deviceRentalRecordPo.getTotalRentalDays(), modificationWaiverRuleJson.getWaiverDays());
                    DeviceProfitLossDetailPo deviceProfitLossDetailPo = new DeviceProfitLossDetailPo();
                    deviceProfitLossDetailPo.setBusinessBillType(BusinessBillTypeEnum.ENTRY);
                    deviceProfitLossDetailPo.setFeeType(FeeTypeEnum.MODIFICATION_FEE_WAIVER);
                    deviceProfitLossDetailPo.setFee(deviceRentalRecordPo.getModificationFeeInfo().getModificationTotalAmount().multiply(BigDecimal.valueOf(modificationWaiverRuleJson.getWaiverRate())));
                    deviceProfitLossDetailPo.setProfitLossRecordId(null);
                    deviceProfitLossDetailPo.setStatus(ProfitLossStatusEnum.ALREADY_EFFECTIVE);
                    deviceProfitLossDetailPo.setDeviceId(deviceRentalRecordPo.getDeviceId());
                    deviceProfitLossDetailPo.setContractId(deviceRentalRecordPo.getContractId());
                    deviceProfitLossDetailPo.setContractNo(deviceRentalRecordPo.getContractNo());
                    deviceProfitLossDetailPo.setDeviceNo(deviceRentalRecordPo.getDeviceNo());
                    deviceProfitLossDetailPo.setDeviceRentalRecordId(deviceRentalRecordPo.getId());
                    deviceProfitLossDetailPo.setFeeIncurredTime(localDate.atStartOfDay());
                    deviceProfitLossDetailPo.setRemarks("系统生成改装费减免损益");
                    deviceProfitLossDetailService.save(deviceProfitLossDetailPo);
                    waiverProfitLossList.add(deviceProfitLossDetailPo);
                }
            }
        }
        return waiverProfitLossList;
    }

    /**
     * 计算损益总金额，并记录损益明细ProfitLossInfoJson
     *
     * @param totalProfitLossAmount        损益总金额
     * @param deviceProfitLossDetailPoList 设备损益明细记录集合
     * @return 损益明细ProfitLossInfoJson
     * @author songkf
     */
    private void getProfitLossInfoJsons(List<ProfitLossInfoJson> profitLossInfoJson, BigDecimal totalProfitLossAmount, List<DeviceProfitLossDetailPo> deviceProfitLossDetailPoList, DeviceRentalRecordPo deviceRentalRecordPo) {
        ProfitLossInfoJson profitLossInfo;
        log.info("[损益总金额：{}]-[损益明细记录集合：{}]-[设备租赁记录ID：{}]", totalProfitLossAmount, deviceProfitLossDetailPoList, deviceRentalRecordPo.getId());
        for (DeviceProfitLossDetailPo deviceProfitLossDetailPo : deviceProfitLossDetailPoList) {
            if (!deviceProfitLossDetailPo.getFeeType().equals(FeeTypeEnum.PAUSE_FEE)) {
                //如果是报停的话，则不需要相加，因为记录的是报停天数,直接累加到设备租赁记录中
                profitLossInfo = new ProfitLossInfoJson();
                profitLossInfo.setFee(deviceProfitLossDetailPo.getFee());
                profitLossInfo.setType(deviceProfitLossDetailPo.getFeeType().getValue());
                profitLossInfo.setTypeDesc(deviceProfitLossDetailPo.getFeeType().getDesc());
                profitLossInfo.setRecordId(deviceProfitLossDetailPo.getProfitLossRecordId());
                profitLossInfo.setDetailId(deviceProfitLossDetailPo.getId());
                profitLossInfo.setBusinessBillType(deviceProfitLossDetailPo.getBusinessBillType().getValue());
                profitLossInfoJson.add(profitLossInfo);
                //判断损益类型，如果是减免费用类型，则减去费用，否则加上费用
                if (WAIVER_FEE_TYPE_LIST.contains(deviceProfitLossDetailPo.getFeeType())) {
                    //补录报停需要处理计租天数以及报停天数。经沟通，补录报停只产生费用，不影响周期，不扣减天数
//                    if (deviceProfitLossDetailPo.getFeeType().equals(FeeTypeEnum.SUPPLEMENTARY_PAUSE_FEE)) {
//                        deviceRentalRecordPo.setTotalPauseDays(deviceRentalRecordPo.getTotalPauseDays() + deviceProfitLossDetailPo.getDays());
//                        deviceRentalRecordPo.setTotalRentalDays(deviceRentalRecordPo.getTotalRentalDays() - deviceProfitLossDetailPo.getDays());
//                    }
                    totalProfitLossAmount = totalProfitLossAmount.subtract(deviceProfitLossDetailPo.getFee());
                } else {
                    totalProfitLossAmount = totalProfitLossAmount.add(deviceProfitLossDetailPo.getFee());
                }

            } else {
                deviceRentalRecordPo.setTotalPauseDays(deviceRentalRecordPo.getTotalPauseDays() + deviceProfitLossDetailPo.getDays());
//                deviceRentalRecordPo.setTotalRemainingPauseDays(deviceRentalRecordPo.getTotalRemainingPauseDays() - deviceProfitLossDetailPo.getFee().doubleValue());
            }
            deviceProfitLossDetailPo.setStatus(ProfitLossStatusEnum.ALREADY_EFFECTIVE);
        }
    }

    //生成非全日租金损益
    private DeviceProfitLossDetailPo generateNonFullDayRentalProfitLoss(FeeTypeEnum feeType, BigDecimal fee, DeviceRentalRecordPo deviceRentalRecordPo, LocalDate handleDate, ProfitLossStatusEnum alreadyEffective) {
        DeviceProfitLossDetailPo deviceProfitLossDetailPo = new DeviceProfitLossDetailPo();
        deviceProfitLossDetailPo.setBusinessBillType(BusinessBillTypeEnum.ENTRY);
        deviceProfitLossDetailPo.setFeeType(feeType);
        deviceProfitLossDetailPo.setFee(fee);
        deviceProfitLossDetailPo.setProfitLossRecordId(deviceRentalRecordPo.getId());
        deviceProfitLossDetailPo.setStatus(alreadyEffective);
        deviceProfitLossDetailPo.setDeviceId(deviceRentalRecordPo.getDeviceId());
        deviceProfitLossDetailPo.setContractId(deviceRentalRecordPo.getContractId());
        deviceProfitLossDetailPo.setContractNo(deviceRentalRecordPo.getContractNo());
        deviceProfitLossDetailPo.setDeviceNo(deviceRentalRecordPo.getDeviceNo());
        deviceProfitLossDetailPo.setDeviceRentalRecordId(deviceRentalRecordPo.getId());
        deviceProfitLossDetailPo.setFeeIncurredTime(handleDate.plusDays(1).atStartOfDay());
        deviceProfitLossDetailPo.setRemarks("系统生成非全日租金损益");
        deviceProfitLossDetailService.save(deviceProfitLossDetailPo);
        return deviceProfitLossDetailPo;
    }
}
